├── .gitignore ├── .semaphore └── semaphore.yml ├── Gemfile ├── README.md ├── Rakefile ├── d3.js ├── demo ├── Gemfile ├── README.md ├── app │ ├── cycling.rb │ ├── data │ │ ├── elections_2016.rb │ │ ├── eu_countries.rb │ │ ├── harry_potter.rb │ │ ├── iphones.rb │ │ ├── london_population.rb │ │ ├── man_vs_horse.rb │ │ ├── mtg_modern_colors.rb │ │ ├── mtg_modern_creatures.rb │ │ ├── olympics_2016_medals.rb │ │ ├── paradox.rb │ │ ├── polish_pms.rb │ │ ├── star_trek_voyager.rb │ │ └── weather_in_london.rb │ ├── elections_2016.rb │ ├── eu_force_graph.rb │ ├── harry_potter.rb │ ├── iphones.rb │ ├── london_population.rb │ ├── london_population_area.rb │ ├── man_vs_horse.rb │ ├── mtg_modern_colors.rb │ ├── mtg_modern_creatures.rb │ ├── olympics_2016_medals.rb │ ├── paradox.rb │ ├── polish_pms.rb │ ├── star_trek_voyager.rb │ ├── temperature.rb │ ├── us_gdp.rb │ └── weather_in_london.rb ├── assets │ ├── d3.js │ └── stylesheets │ │ ├── cycling.css │ │ ├── elections_2016.css │ │ ├── london_population.css │ │ ├── london_population_area.css │ │ ├── temperature.css │ │ └── us_gdp.css ├── config.ru └── views │ ├── index.erb │ └── visualization.erb ├── lib ├── opal-d3.rb └── opal │ ├── d3.rb │ └── d3 │ ├── arc.rb │ ├── area.rb │ ├── axis.rb │ ├── band_scale.rb │ ├── collections.rb │ ├── color.rb │ ├── continuous_scale.rb │ ├── creator.rb │ ├── curve.rb │ ├── dsv.rb │ ├── ease.rb │ ├── force.rb │ ├── format.rb │ ├── histograms.rb │ ├── interpolate.rb │ ├── line.rb │ ├── map.rb │ ├── misc.rb │ ├── native.rb │ ├── nest.rb │ ├── ordinal_scale.rb │ ├── path.rb │ ├── pie.rb │ ├── point_scale.rb │ ├── polygon.rb │ ├── quadtree.rb │ ├── quantile_scale.rb │ ├── quantize_scale.rb │ ├── radial_area.rb │ ├── radial_line.rb │ ├── random.rb │ ├── request.rb │ ├── search.rb │ ├── selection.rb │ ├── sequential_scale.rb │ ├── set.rb │ ├── stack.rb │ ├── statistics.rb │ ├── symbol.rb │ ├── threshold_scale.rb │ ├── time_format.rb │ ├── time_interval.rb │ ├── transformations.rb │ └── version.rb ├── opal-d3.gemspec └── spec ├── arc_spec.rb ├── area_spec.rb ├── axis_spec.rb ├── band_scale_spec.rb ├── color_spec.rb ├── continuous_scale_spec.rb ├── coverage_spec.rb ├── creator_spec.rb ├── curve_spec.rb ├── dsv_spec.rb ├── ease_spec.rb ├── format_spec.rb ├── histograms_spec.rb ├── html ├── d3.js └── index.html.erb ├── interpolate_spec.rb ├── line_spec.rb ├── map_spec.rb ├── misc_spec.rb ├── nest_spec.rb ├── objects_spec.rb ├── ordinal_scale_spec.rb ├── path_spec.rb ├── pie_spec.rb ├── point_scale_spec.rb ├── polygon_spec.rb ├── quadtree_spec.rb ├── quantile_scale_spec.rb ├── quantize_scale_spec.rb ├── radial_area_spec.rb ├── radial_line_spec.rb ├── random_spec.rb ├── request_spec.rb ├── search_spec.rb ├── selection_data_spec.rb ├── selection_manipulation_spec.rb ├── selection_spec.rb ├── sequential_scale_spec.rb ├── set_spec.rb ├── spec_helper.rb ├── stack_spec.rb ├── statistics_spec.rb ├── symbol_spec.rb ├── threshold_scale_spec.rb ├── time_format_spec.rb ├── time_interval_spec.rb └── transformations_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | opal-d3*.gem -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: Ruby 3 | agent: 4 | machine: 5 | type: e1-standard-2 6 | os_image: ubuntu2004 7 | blocks: 8 | - name: bundle exec rspec 9 | task: 10 | jobs: 11 | - name: bundle install 12 | commands: 13 | - checkout 14 | - 'sem-version ruby 2.7.8 # doesn''t work on ruby 3.x' 15 | - bundle install --path vendor/bundle 16 | - bundle exec rake test 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Opal Ruby interface for D3 [ https://d3js.org/ ]. 2 | 3 | It implements interface fairly closely following javascript API, except with blocks, classes, no camelcase etc. 4 | 5 | About 60% of D3 APIs are implemented. Pull requests for the rest welcome. 6 | 7 | For some examples check `demo` directory or [visit demo site](https://taw.github.io/opal-d3/). 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler.require 3 | Bundler::GemHelper.install_tasks 4 | 5 | require "opal/rspec/rake_task" 6 | Opal::RSpec::RakeTask.new(:test) do |server, task| 7 | ENV["SPEC_OPTS"] = "--color --require spec_helper" 8 | server.index_path = "spec/html/index.html.erb" 9 | server.append_path "lib" 10 | end 11 | 12 | desc "Create static snapshot of the demo page" 13 | task :snapshot do 14 | dir = Pathname("snapshot") 15 | system "trash", dir.to_s if dir.exist? 16 | dir.mkpath 17 | Dir.chdir(dir) do 18 | system "wget --mirror -np http://localhost:9292/" 19 | end 20 | end 21 | 22 | desc "Build gem" 23 | task "gem:build" do 24 | system "gem build opal-d3.gemspec" 25 | end 26 | 27 | desc "Upload gem" 28 | task "gem:push" => "gem:build" do 29 | gem_file = Dir["opal-d3-*.gem"][-1] or raise "No gem found" 30 | system "gem", "push", gem_file 31 | end 32 | -------------------------------------------------------------------------------- /demo/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "sinatra" 4 | gem "opal-d3", path: ".." 5 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | To give it a try: 2 | 3 | ``` 4 | cd demo 5 | bundle install 6 | bundle exec rackup 7 | ``` 8 | -------------------------------------------------------------------------------- /demo/app/cycling.rb: -------------------------------------------------------------------------------- 1 | # Inspired by freecodecamp project 2 | # This visualization really stretches what opal-D3 can currently comfortably do 3 | # The code will hopefully become nicer in future versions of the gem 4 | require "opal-d3" 5 | require "json" 6 | 7 | url = "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/cyclist-data.json" 8 | 9 | D3.json(url) do |error, response| 10 | # This interface could surely be better 11 | data = JSON.parse(`JSON.stringify(response)`) 12 | 13 | visualization = D3.select("#visualization") 14 | svg = visualization.append("svg") 15 | width = svg.style("width").to_i 16 | height = svg.style("height").to_i 17 | margin_left = 50 18 | margin_right = 50 19 | margin_top = 50 20 | margin_bottom = 50 21 | 22 | format_ms = proc do |s| 23 | "%d:%02d" % [(s/60).floor, s%60] 24 | end 25 | 26 | ydomain = D3.extent(data){|d| d[:Seconds] } 27 | xdomain = D3.extent(data){|d| d[:Year] } 28 | 29 | xscale = D3.scale_linear 30 | .domain(xdomain) 31 | .range([margin_left, width - margin_right]) 32 | .nice 33 | yscale = D3.scale_linear 34 | .domain(ydomain) 35 | .range([height - margin_bottom, margin_top]) 36 | .nice 37 | 38 | xaxis = D3.axis_bottom(xscale).tick_format(&D3.format("d")) 39 | D3.select("svg").append("g").attr("id", "x-axis") 40 | .style("transform", "translate(0, #{height - margin_bottom}px)") 41 | .call(xaxis) 42 | 43 | yaxis = D3.axis_left(yscale).tick_format(&format_ms) 44 | D3.select("svg").append("g").attr("id", "y-axis") 45 | .style("transform", "translate(#{margin_left}px, 0)") 46 | .call(yaxis) 47 | 48 | tooltip = visualization.append("tooltip").attr("id", "tooltip") 49 | 50 | D3.select("svg").select_all("circle") 51 | .data(data).enter 52 | .append("circle") 53 | .attr("class"){|d| d[:Doping] != "" ? "dot doping" : "dot" } 54 | .attr("cx"){|d| xscale.(d[:Year]) } 55 | .attr("cy"){|d| yscale.(d[:Seconds]) } 56 | .attr("r", 3) 57 | .on("mouseover"){|d| 58 | tooltip 59 | .style("display", "block") 60 | .style("left", "#{xscale.(d[:Year]) + 15}px") 61 | .style("top", "#{yscale.(d[:Seconds]) - 10}px") 62 | .html("#{d[:Name]} - #{d[:Nationality]}
#{d[:Doping]}") 63 | } 64 | .on("mouseout"){|d| 65 | tooltip 66 | .style("display", "none") 67 | } 68 | 69 | legend = visualization.append("div").attr("id", "legend") 70 | legend.append("div").text("Red - doping suspicions") 71 | legend.append("div").text("Blue - no doping suspicions") 72 | end 73 | -------------------------------------------------------------------------------- /demo/app/data/elections_2016.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | Elections2016 = [ 4 | { name: "Donald Trump", votes: 62_979_879, color: "#ff8c00" }, 5 | { name: "Hillary Clinton", votes: 65_844_954, color: "#98abc5" }, 6 | { name: "Gary Johnson", votes: 4_488_919, color: "#8a89a6" }, 7 | { name: "Jill Stein", votes: 1_457_044, color: "#7b6888" }, 8 | { name: "Evan McMullin", votes: 725_902, color: "#6b486b" }, 9 | { name: "Darrell Castle", votes: 202_979, color: "#a05d56" }, 10 | ].map{|o| OpenStruct.new(o) } 11 | -------------------------------------------------------------------------------- /demo/app/data/eu_countries.rb: -------------------------------------------------------------------------------- 1 | EUCountries = [ 2 | {id: "at", label: "Austria"}, 3 | {id: "be", label: "Belgium"}, 4 | {id: "bg", label: "Bulgaria"}, 5 | {id: "cr", label: "Croatia"}, 6 | {id: "cy", label: "Cyprus"}, 7 | {id: "cz", label: "Czechia"}, 8 | {id: "de", label: "Germany"}, 9 | {id: "dk", label: "Denmark"}, 10 | {id: "ee", label: "Estonia"}, 11 | {id: "fi", label: "Finland"}, 12 | {id: "fr", label: "France"}, 13 | {id: "gr", label: "Greece"}, 14 | {id: "hu", label: "Hungary"}, 15 | {id: "ie", label: "Ireland"}, 16 | {id: "it", label: "Italy"}, 17 | {id: "lt", label: "Lithuania"}, 18 | {id: "lu", label: "Luxembourg"}, 19 | {id: "lv", label: "Latvia"}, 20 | {id: "mt", label: "Malta"}, 21 | {id: "nl", label: "Netherlands"}, 22 | {id: "pl", label: "Poland"}, 23 | {id: "pt", label: "Portugal"}, 24 | {id: "ro", label: "Romania"}, 25 | {id: "se", label: "Sweden"}, 26 | {id: "sk", label: "Slovakia"}, 27 | {id: "sl", label: "Slovenia"}, 28 | {id: "sp", label: "Spain"}, 29 | {id: "uk", label: "United Kingdom"}, 30 | ] 31 | 32 | EUCountriesNeighbours = [ 33 | {source: "at", target: "cz"}, 34 | {source: "at", target: "de"}, 35 | {source: "at", target: "it"}, 36 | {source: "be", target: "de"}, 37 | {source: "be", target: "lu"}, 38 | {source: "bg", target: "gr"}, 39 | {source: "bg", target: "ro"}, 40 | {source: "cz", target: "pl"}, 41 | {source: "cz", target: "sk"}, 42 | {source: "dk", target: "de"}, 43 | {source: "dk", target: "se"}, 44 | {source: "fr", target: "de"}, 45 | {source: "fr", target: "it"}, 46 | {source: "fr", target: "sp"}, 47 | {source: "gr", target: "cy"}, 48 | {source: "hu", target: "at"}, 49 | {source: "hu", target: "cr"}, 50 | {source: "hu", target: "ro"}, 51 | {source: "hu", target: "sk"}, 52 | {source: "hu", target: "sl"}, 53 | {source: "lt", target: "lv"}, 54 | {source: "lu", target: "de"}, 55 | {source: "lu", target: "fr"}, 56 | {source: "lu", target: "fr"}, 57 | {source: "lv", target: "ee"}, 58 | {source: "mt", target: "it"}, 59 | {source: "nl", target: "be"}, 60 | {source: "nl", target: "de"}, 61 | {source: "pl", target: "de"}, 62 | {source: "pl", target: "lt"}, 63 | {source: "pl", target: "sk"}, 64 | {source: "pt", target: "sp"}, 65 | {source: "se", target: "fi"}, 66 | {source: "sk", target: "at"}, 67 | {source: "sl", target: "cr"}, 68 | {source: "sl", target: "it"}, 69 | {source: "uk", target: "fr"}, 70 | {source: "uk", target: "ie"}, 71 | ] 72 | -------------------------------------------------------------------------------- /demo/app/data/harry_potter.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | require "time" 3 | 4 | # Pages for UK releases, according to Harry Potter wikia 5 | HarryPotterBooks = [ 6 | ["Philosopher's Stone", "1997-06-26", 223], 7 | ["Chamber of Secrets", "1998-07-02", 251], 8 | ["Prisoner of Azkaban", "1999-07-08", 317], 9 | ["Goblet of Fire", "2000-07-08", 636], 10 | ["Order of the Phoenix", "2003-06-21", 766], 11 | ["Half-Blood Prince", "2005-07-16", 607], 12 | ["Deathly Hallows", "2007-07-21", 610], 13 | ].map do |title, time, pages| 14 | OpenStruct.new( 15 | title: title, 16 | date: Time.parse(time), 17 | pages: pages 18 | ) 19 | end 20 | -------------------------------------------------------------------------------- /demo/app/data/iphones.rb: -------------------------------------------------------------------------------- 1 | require "date" 2 | require "time" 3 | require "ostruct" 4 | 5 | IPhones = [ 6 | ["iPhone", "2007-06-29", [4,8,16]], 7 | ["iPhone 3G", "2008-07-11", [8,16]], 8 | ["iPhone 3GS", "2009-06-19", [8,16,32]], 9 | ["iPhone 4", "2010-06-24", [8,16,32]], 10 | ["iPhone 4S", "2011-10-14", [8,16,32,64]], 11 | ["iPhone 5", "2012-09-21", [16,32,64]], 12 | ["iPhone 5C", "2013-09-20", [8,16,32]], 13 | ["iPhone 5S", "2013-09-20", [16,32,64]], 14 | ["iPhone 6", "2014-09-19", [16,64,128]], 15 | ["iPhone 6 Plus", "2014-09-19", [16,64,128]], 16 | ["iPhone 6S", "2015-09-25", [16,32,64,128]], 17 | ["iPhone 6S Plus", "2015-09-25", [16,32,64,128]], 18 | ["iPhone SE", "2016-03-31", [16,64]], 19 | ["iPhone 7", "2016-09-16", [32,128,256]], 20 | ["iPhone 7 Plus", "2016-09-16", [32,128,256]], 21 | ].map{|n,d,s| 22 | OpenStruct.new(name: n, released: Time.parse(d), sizes: s) 23 | } 24 | 25 | IPhoneVariants = IPhones.flat_map do |i| 26 | i.sizes.map do |sz| 27 | OpenStruct.new(name: i.name, released: i.released, size: sz) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /demo/app/data/london_population.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | LondonPopulation = [ 4 | {"year": 1801, "inner": 879491, "outer": 131666, "greater": 1011157 }, 5 | {"year": 1811, "inner": 1040033, "outer": 157640, "greater": 1197673 }, 6 | {"year": 1821, "inner": 1263975, "outer": 186147, "greater": 1450122 }, 7 | {"year": 1831, "inner": 1515557, "outer": 214392, "greater": 1729949 }, 8 | {"year": 1841, "inner": 1661346, "outer": 255667, "greater": 1917013 }, 9 | {"year": 1851, "inner": 1995846, "outer": 290763, "greater": 2286609 }, 10 | {"year": 1861, "inner": 2634143, "outer": 460248, "greater": 3094391 }, 11 | {"year": 1871, "inner": 3272441, "outer": 629737, "greater": 3902178 }, 12 | {"year": 1881, "inner": 3910735, "outer": 799225, "greater": 4709960 }, 13 | {"year": 1891, "inner": 4422340, "outer": 1143516, "greater": 5565856 }, 14 | {"year": 1901, "inner": 4670177, "outer": 1556317, "greater": 6226494 }, 15 | {"year": 1911, "inner": 4997741, "outer": 2160134, "greater": 7157875 }, 16 | {"year": 1921, "inner": 4936803, "outer": 2616723, "greater": 7553526 }, 17 | {"year": 1931, "inner": 4887932, "outer": 3211010, "greater": 8098942 }, 18 | {"year": 1941, "inner": 4224135, "outer": 3763801, "greater": 7987936 }, 19 | {"year": 1951, "inner": 3680821, "outer": 4483595, "greater": 8164416 }, 20 | {"year": 1961, "inner": 3336557, "outer": 4444785, "greater": 7781342 }, 21 | {"year": 1971, "inner": 3030490, "outer": 4418694, "greater": 7449184 }, 22 | {"year": 1981, "inner": 2425534, "outer": 4182979, "greater": 6608513 }, 23 | {"year": 1991, "inner": 2625245, "outer": 4262035, "greater": 6887280 }, 24 | {"year": 2001, "inner": 2765975, "outer": 4406061, "greater": 7172036 }, 25 | {"year": 2011, "inner": 3231900, "outer": 4942100, "greater": 8173900 }, 26 | ].map{|o| OpenStruct.new(o)} 27 | -------------------------------------------------------------------------------- /demo/app/data/man_vs_horse.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | ManVsHorse = [ 4 | [2017, "Horse", "Iola Evans", "Rheidol Petra", "2:23:03", "Owen Beilby", "2:50:21"], 5 | [2016, "Horse", "Lindsey Walters", "Deliva Crianza", "2:17:58", "Ross MacDonald", "2:37:51"], 6 | [2015, "Horse", "Geoffrey Allen", "Leo", "2:20:46", "Hugh Aggleton", "2:30:35"], 7 | [2014, "Horse", "Geoffrey Allen", "Leo", "2:22:53", "Jonathan Albon", "2:42:49"], 8 | [2013, "Horse", "Beti Gordon", "Next in line Grangeway", "2:18:03", "Hugh Aggleton", "2:46:25"], 9 | [2012, "Horse", "Iola Evans", "Rheidol Star", "2:00:51", "Huw Lobb", "2:25:57"], 10 | [2011, "Horse", "Beti Gordon", "Next in line Grangeway", "2:08:37", "Charlie Pearson", "2:25:45"], 11 | [2010, "Horse", "Llinos Mair Jones", "Sly Dai", "2:07:04", "Haggai Chepkwony", "2:17:27"], 12 | [2009, "Horse", "Geoffrey Allen", "Dukes Touch of Fun", "2:11:43", "Martin Cox", "2:20:02"], 13 | [2008, "Horse", "Geoffrey Allen", "Dukes Touch of Fun", "2:18:13", "John Macfarlane", "2:18:43"], 14 | [2007, "Human", "Geoffrey Allen", "Lucy", "2:31:26", "Florian Holzinger", "2:20:30"], 15 | [2006, "Horse", "Denise Meldrum", "Tarran Bay", "2:10:29", "Haggai Chepkwony", "2:19:06"], 16 | [2005, "Horse", "Lise Cooke", "Gifted Lady", "2:19:11", "Stephen Goulding", "2:33:22"], 17 | [2004, "Human", "Zoe White", "Kay Bee Jay", "2:07:36", "Huw Lobb", "2:05:19"], 18 | [2003, "Horse", "Robyn Petrie-Ritchie", "Druimguiga Shemal", "2:02:01", "Mark Croasdale", "2:19:02"], 19 | [2002, "Horse", "Robyn Petrie-Ritchie", "Druimguiga Shemal", "2:02:23", "James McQueen", "2:18:52"], 20 | [2001, "Horse", "Heather Evans", "Royal Mikado", "2:08:06", "Martin Cox", "2:17:17"], 21 | [2000, "Horse", "Heather Evans", "Royal Mikado", "2:08", "Mark Croasdale", "2:10"], 22 | [1999, "Horse", "Jackie Gilmour", "Ruama", "1:58", "Mark Palmer", "2:16"], 23 | [1998, "Horse", "Jackie Gilmour", "Ruama", "1:46", "Stuart Major", "2:16"], 24 | [1997, "Horse", "Megan Lewis", "unknown", "1:52", "Paul Cadwallader", "2:09"], 25 | [1996, "Horse", "Ken Mapp", "Ahmaar", "1:57", "Mark Palmer", "2:12"], 26 | [1995, "Horse", "Ken Mapp", "Ahmaar", "1:57", "Paul Cadwallader", "2:06"], 27 | [1994, "Horse", "Celia Tymons", "Eskalabar", "1:52", "Mark Croasdale", "2:09"], 28 | [1993, "Horse", "John Hudson", "unknown", "1:47", "Robin Bergstrand", "2:03"], 29 | [1992, "Horse", "Zoe Jennings", "Hussar", "1:38", "Derek Green", "2:09"], 30 | [1991, "Horse", "Zoe Jennings", "Hussar", "1:30", "Mark Croasdale", "2:05"], 31 | [1990, "Horse", "Ray Jenkins", "The Doid", "1:36", "Glyn Williams", "2:07"], 32 | [1989, "Horse", "Ray Jenkins", "The Doid", "1:54", "Mark Croasdale", "2:10"], 33 | [1988, "Horse", "John Davies", "Mavies", "1:47", "Mark Croasdale", "2:08"], 34 | [1987, "Horse", "Ray Jenkins", "The Doid", "1:32", "Paul Wheeler", "1:57"], 35 | [1986, "Horse", "Nia Tudno-Jones", "Jenny", "1:44", "Fuselier Hughes", "2:08"], 36 | [1985, "Horse", "Nia Tudno-Jones", "Jenny", "1:40", "David Woodhead", "2:08"], 37 | [1984, "Horse", "William Jones", "Solitaire", "1:20", "David Woodhead", "2:05"], 38 | [1983, "Horse", "Ann Thomas", "Nutmeg", "1:26", "Dic Evans", "2:02"], 39 | [1982, "Horse", "Sue Thomas", "Simon", "2:06", "Paul Brownson", "2:10"], 40 | [1981, "Horse", "Clive Powell", "Sultan", "2:02", "Dic Evans", "2:24"], 41 | [1980, "Horse", "Glyn Jones", "Solomon", "1:27", "Dic Evans", "2:10"], 42 | ].map{|y,w,rn,hn,ht,mn,mt| 43 | h,m,s = ht.split(":").map(&:to_i) 44 | htm = h*60 + m # +(s||0) 45 | h,m,s = mt.split(":").map(&:to_i) 46 | mtm = h*60 + m # +(s||0) 47 | OpenStruct.new( 48 | year: y, 49 | winner: w, 50 | rider: rn, 51 | horse: hn, 52 | horse_time: ht, 53 | horse_time_min: htm, 54 | human: mn, 55 | human_time: mt, 56 | human_time_min: mtm, 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /demo/app/data/mtg_modern_colors.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | # As of AER 4 | MtgModernColors = [ 5 | [0, "b", 3], 6 | [0, "g", 1], 7 | [0, "r", 2], 8 | [0, "u", 3], 9 | [0, "w", 2], 10 | [0, "x", 29], 11 | [1, "b", 180], 12 | [1, "g", 198], 13 | [1, "m", 35], 14 | [1, "r", 203], 15 | [1, "u", 161], 16 | [1, "w", 215], 17 | [1, "x", 148], 18 | [2, "b", 293], 19 | [2, "g", 324], 20 | [2, "m", 237], 21 | [2, "r", 303], 22 | [2, "u", 332], 23 | [2, "w", 392], 24 | [2, "x", 266], 25 | [3, "b", 364], 26 | [3, "g", 344], 27 | [3, "m", 287], 28 | [3, "r", 369], 29 | [3, "u", 344], 30 | [3, "w", 347], 31 | [3, "x", 328], 32 | [4, "b", 300], 33 | [4, "g", 293], 34 | [4, "m", 225], 35 | [4, "r", 316], 36 | [4, "u", 311], 37 | [4, "w", 299], 38 | [4, "x", 201], 39 | [5, "b", 225], 40 | [5, "g", 214], 41 | [5, "m", 205], 42 | [5, "r", 202], 43 | [5, "u", 197], 44 | [5, "w", 182], 45 | [5, "x", 109], 46 | [6, "b", 119], 47 | [6, "g", 120], 48 | [6, "m", 116], 49 | [6, "r", 125], 50 | [6, "u", 115], 51 | [6, "w", 87], 52 | [6, "x", 77], 53 | [7, "b", 48], 54 | [7, "g", 58], 55 | [7, "m", 47], 56 | [7, "r", 36], 57 | [7, "u", 42], 58 | [7, "w", 45], 59 | [7, "x", 44], 60 | [8, "b", 12], 61 | [8, "g", 23], 62 | [8, "m", 20], 63 | [8, "r", 16], 64 | [8, "u", 11], 65 | [8, "w", 10], 66 | [8, "x", 18], 67 | [9, "b", 8], 68 | [9, "g", 5], 69 | [9, "r", 6], 70 | [9, "u", 8], 71 | [9, "w", 5], 72 | [9, "x", 9], 73 | [10, "b", 2], 74 | [10, "g", 2], 75 | [10, "m", 2], 76 | [10, "r", 2], 77 | [10, "u", 8], 78 | [10, "w", 2], 79 | [10, "x", 6], 80 | [11, "g", 1], 81 | [11, "u", 1], 82 | [11, "x", 6], 83 | [12, "m", 1], 84 | [12, "u", 1], 85 | [12, "x", 2], 86 | [13, "x", 1], 87 | [14, "u", 1], 88 | [15, "m", 1], 89 | [15, "x", 1], 90 | ].map do |cmc, color, count| 91 | OpenStruct.new( 92 | cmc: cmc, 93 | color: color, 94 | count: count, 95 | ) 96 | end 97 | -------------------------------------------------------------------------------- /demo/app/data/mtg_modern_creatures.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | # As of AER 4 | # get rid of all *-cards 5 | MtgModernCreatures = [ 6 | [-1, -1, 1], 7 | [-1, 3, 1], 8 | ["*", "*", 70], 9 | ["*", 1, 2], 10 | ["*", "1+*", 2], 11 | ["*", 3, 3], 12 | ["*", 4, 1], 13 | ["*", 5, 1], 14 | [0, "*", 1], 15 | [0, 0, 86], 16 | [0, 1, 47], 17 | [0, 2, 32], 18 | [0, 3, 42], 19 | [0, 4, 41], 20 | [0, 5, 18], 21 | [0, 6, 9], 22 | [0, 7, 2], 23 | [0, 8, 2], 24 | [0, 13, 2], 25 | [1, 1, 757], 26 | [1, 2, 178], 27 | [1, 3, 147], 28 | [1, 4, 70], 29 | [1, 5, 23], 30 | [1, 6, 4], 31 | [1, 7, 2], 32 | [1, 8, 1], 33 | ["1+*", "1+*", 1], 34 | [2, "*", 1], 35 | [2, 0, 1], 36 | [2, 1, 423], 37 | [2, 2, 956], 38 | [2, 3, 271], 39 | [2, 4, 111], 40 | [2, 5, 35], 41 | [2, 6, 5], 42 | [2, 7, 3], 43 | [2, 8, 2], 44 | [2, 10, 1], 45 | [3, 1, 111], 46 | [3, 2, 219], 47 | [3, 3, 532], 48 | [3, 4, 121], 49 | [3, 5, 49], 50 | [3, 6, 17], 51 | [3, 7, 4], 52 | [3, 8, 1], 53 | [4, 1, 31], 54 | [4, 2, 53], 55 | [4, 3, 106], 56 | [4, 4, 338], 57 | [4, 5, 75], 58 | [4, 6, 27], 59 | [4, 7, 7], 60 | [4, 8, 1], 61 | [4, 9, 2], 62 | [5, 1, 11], 63 | [5, 2, 16], 64 | [5, 3, 40], 65 | [5, 4, 77], 66 | [5, 5, 203], 67 | [5, 6, 34], 68 | [5, 7, 17], 69 | [5, 8, 3], 70 | [5, 9, 1], 71 | [6, 1, 8], 72 | [6, 2, 4], 73 | [6, 3, 8], 74 | [6, 4, 32], 75 | [6, 5, 31], 76 | [6, 6, 125], 77 | [6, 7, 8], 78 | [6, 8, 2], 79 | [6, 9, 1], 80 | [7, 2, 3], 81 | [7, 3, 1], 82 | [7, 4, 5], 83 | [7, 5, 7], 84 | [7, 6, 13], 85 | [7, 7, 47], 86 | [7, 8, 2], 87 | [7, 10, 1], 88 | [7, 11, 2], 89 | [8, 0, 1], 90 | [8, 4, 1], 91 | [8, 5, 3], 92 | [8, 7, 1], 93 | [8, 8, 30], 94 | [8, 9, 1], 95 | [9, 5, 1], 96 | [9, 7, 2], 97 | [9, 8, 1], 98 | [9, 9, 11], 99 | [9, 10, 1], 100 | [9, 14, 1], 101 | [10, 2, 1], 102 | [10, 8, 1], 103 | [10, 9, 1], 104 | [10, 10, 13], 105 | [11, 9, 1], 106 | [11, 11, 4], 107 | [12, 12, 3], 108 | [13, 13, 5], 109 | [15, 15, 2], 110 | ].map do |p,t,c| 111 | OpenStruct.new( 112 | power: p, 113 | toughness: t, 114 | count: c, 115 | ) 116 | end.select{|cg| cg.power.is_a?(Integer) and cg.toughness.is_a?(Integer) } 117 | -------------------------------------------------------------------------------- /demo/app/data/olympics_2016_medals.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | Olympics2016Medals = [ 4 | ["United States", "USA", 46, 37, 38], 5 | ["Great Britain", "GBR", 27, 23, 17], 6 | ["China", "CHN", 26, 18, 26], 7 | ["Russia", "RUS", 19, 17, 19], 8 | ["Germany", "GER", 17, 10, 15], 9 | ["Japan", "JPN", 12, 8, 21], 10 | ["France", "FRA", 10, 18, 14], 11 | ["South Korea", "KOR", 9, 3, 9], 12 | ["Italy", "ITA", 8, 12, 8], 13 | ["Australia", "AUS", 8, 11, 10], 14 | ["Netherlands", "NED", 8, 7, 4], 15 | ["Hungary", "HUN", 8, 3, 4], 16 | ["Brazil", "BRA", 7, 6, 6], 17 | ["Spain", "ESP", 7, 4, 6], 18 | ["Kenya", "KEN", 6, 6, 1], 19 | ["Jamaica", "JAM", 6, 3, 2], 20 | ["Croatia", "CRO", 5, 3, 2], 21 | ["Cuba", "CUB", 5, 2, 4], 22 | ["New Zealand", "NZL", 4, 9, 5], 23 | ["Canada", "CAN", 4, 3, 15], 24 | ["Uzbekistan", "UZB", 4, 2, 7], 25 | ["Kazakhstan", "KAZ", 3, 5, 9], 26 | ["Colombia", "COL", 3, 2, 3], 27 | ["Switzerland", "SUI", 3, 2, 2], 28 | ["Iran", "IRI", 3, 1, 4], 29 | ["Greece", "GRE", 3, 1, 2], 30 | ["Argentina", "ARG", 3, 1, 0], 31 | ["Denmark", "DEN", 2, 6, 7], 32 | ["Sweden", "SWE", 2, 6, 3], 33 | ["South Africa", "RSA", 2, 6, 2], 34 | ["Ukraine", "UKR", 2, 5, 4], 35 | ["Serbia", "SRB", 2, 4, 2], 36 | ["Poland", "POL", 2, 3, 6], 37 | ["North Korea", "PRK", 2, 3, 2], 38 | ["Belgium", "BEL", 2, 2, 2], 39 | ["Thailand", "THA", 2, 2, 2], 40 | ["Slovakia", "SVK", 2, 2, 0], 41 | ["Georgia", "GEO", 2, 1, 4], 42 | ["Azerbaijan", "AZE", 1, 7, 10], 43 | ["Belarus", "BLR", 1, 4, 4], 44 | ["Turkey", "TUR", 1, 3, 4], 45 | ["Armenia", "ARM", 1, 3, 0], 46 | ["Czech Republic", "CZE", 1, 2, 7], 47 | ["Ethiopia", "ETH", 1, 2, 5], 48 | ["Slovenia", "SLO", 1, 2, 1], 49 | ["Indonesia", "INA", 1, 2, 0], 50 | ["Romania", "ROU", 1, 1, 2], 51 | ["Bahrain", "BRN", 1, 1, 0], 52 | ["Vietnam", "VIE", 1, 1, 0], 53 | ["Chinese Taipei", "TPE", 1, 0, 2], 54 | ["Bahamas", "BAH", 1, 0, 1], 55 | ["Côte d'Ivoire", "CIV", 1, 0, 1], 56 | ["Independent", "IOA", 1, 0, 1], 57 | ["Fiji", "FIJ", 1, 0, 0], 58 | ["Jordan", "JOR", 1, 0, 0], 59 | ["Kosovo", "KOS", 1, 0, 0], 60 | ["Puerto Rico", "PUR", 1, 0, 0], 61 | ["Singapore", "SIN", 1, 0, 0], 62 | ["Tajikistan", "TJK", 1, 0, 0], 63 | ["Malaysia", "MAS", 0, 4, 1], 64 | ["Mexico", "MEX", 0, 3, 2], 65 | ["Algeria", "ALG", 0, 2, 0], 66 | ["Ireland", "IRL", 0, 2, 0], 67 | ["Lithuania", "LTU", 0, 1, 3], 68 | ["Bulgaria", "BUL", 0, 1, 2], 69 | ["Venezuela", "VEN", 0, 1, 2], 70 | ["India", "IND", 0, 1, 1], 71 | ["Mongolia", "MGL", 0, 1, 1], 72 | ["Burundi", "BDI", 0, 1, 0], 73 | ["Grenada", "GRN", 0, 1, 0], 74 | ["Niger", "NIG", 0, 1, 0], 75 | ["Philippines", "PHI", 0, 1, 0], 76 | ["Qatar", "QAT", 0, 1, 0], 77 | ["Norway", "NOR", 0, 0, 4], 78 | ["Egypt", "EGY", 0, 0, 3], 79 | ["Tunisia", "TUN", 0, 0, 3], 80 | ["Israel", "ISR", 0, 0, 2], 81 | ["Austria", "AUT", 0, 0, 1], 82 | ["Dominican Republic", "DOM", 0, 0, 1], 83 | ["Estonia", "EST", 0, 0, 1], 84 | ["Finland", "FIN", 0, 0, 1], 85 | ["Morocco", "MAR", 0, 0, 1], 86 | ["Moldova", "MDA", 0, 0, 1], 87 | ["Nigeria", "NGR", 0, 0, 1], 88 | ["Portugal", "POR", 0, 0, 1], 89 | ["Trinidad and Tobago", "TTO", 0, 0, 1], 90 | ["United Arab Emirates", "UAE", 0, 0, 1], 91 | ].map{|n,a,g,s,b| 92 | OpenStruct.new( 93 | name: n, 94 | acronym: a, 95 | gold: g, 96 | silver: s, 97 | bronze: b, 98 | total: g+s+b, 99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /demo/app/data/paradox.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | require "time" 3 | 4 | ParadoxGames = [ 5 | ["Crusader Kings", 1, "", "2004-08-27"], 6 | ["Crusader Kings", 1, "Deus Vult", "2007-10-04"], 7 | ["Crusader Kings", 2, "", "2012-02-14"], 8 | ["Crusader Kings", 2, "Sword of Islam", "2012-06-26"], 9 | ["Crusader Kings", 2, "Legacy of Rome", "2012-10-16"], 10 | ["Crusader Kings", 2, "Sunset Invasion", "2012-11-15"], 11 | ["Crusader Kings", 2, "The Republic", "2013-01-12"], 12 | ["Crusader Kings", 2, "The Old Gods", "2013-05-28"], 13 | ["Crusader Kings", 2, "Sons of Abraham", "2013-11-18"], 14 | ["Crusader Kings", 2, "Charlemagne", "2014-10-14"], 15 | ["Crusader Kings", 2, "Rajas of India", "2014-03-25"], 16 | ["Crusader Kings", 2, "Way of Life", "2014-12-16"], 17 | ["Crusader Kings", 2, "Horse Lords", "2015-07-14"], 18 | ["Crusader Kings", 2, "Conclave", "2016-02-02"], 19 | ["Crusader Kings", 2, "The Reaper's Due", "2016-08-25"], 20 | ["Europa Universalis", 1, "", "2000-02-20"], 21 | ["Europa Universalis", 2, "", "2001-12-11"], 22 | ["Europa Universalis", 2, "Asia Chapters", "2004-03-25"], 23 | ["Europa Universalis", 3, "", "2007-01-23"], 24 | ["Europa Universalis", 3, "Napoleon's Ambition", "2007-08-22"], 25 | ["Europa Universalis", 3, "In Nomine", "2008-05-28"], 26 | ["Europa Universalis", 3, "Heir to the Throne", "2009-12-15"], 27 | ["Europa Universalis", 3, "Divine Wind", "2010-12-14"], 28 | ["Europa Universalis", 4, "", "2013-08-13"], 29 | ["Europa Universalis", 4, "Conquest of Paradise", "2014-01-14"], 30 | ["Europa Universalis", 4, "Wealth of Nations", "2014-05-29"], 31 | ["Europa Universalis", 4, "Res Publica", "2014-07-16"], 32 | ["Europa Universalis", 4, "Art of War", "2014-10-30"], 33 | ["Europa Universalis", 4, "El Dorado", "2015-02-26"], 34 | ["Europa Universalis", 4, "Common Sense", "2015-06-09"], 35 | ["Europa Universalis", 4, "The Cossacks", "2015-12-01"], 36 | ["Europa Universalis", 4, "Mare Nostrum", "2016-04-05"], 37 | ["Europa Universalis", 4, "Rights of Man", "2016-10-11"], 38 | ["Hearts of Iron", 1, "", "2002-11-24"], 39 | ["Hearts of Iron", 2, "", "2005-01-04"], 40 | ["Hearts of Iron", 2, "Armageddon", "2007-03-29"], 41 | ["Hearts of Iron", 2, "Doomsday", "2006-04-04"], 42 | ["Hearts of Iron", 2, "Iron Cross", "2010-10-07"], 43 | ["Hearts of Iron", 3, "", "2009-08-07"], 44 | ["Hearts of Iron", 3, "Semper Fi", "2010-06-06"], 45 | ["Hearts of Iron", 3, "For the Motherland", "2011-06-29"], 46 | ["Hearts of Iron", 3, "Their Finest Hour", "2012-09-29"], 47 | ["Hearts of Iron", 4, "", "2016-06-06"], 48 | ["Victoria", 1, "", "2003-08-13"], 49 | ["Victoria", 1, "Revolutions", "2006-08-17"], 50 | ["Victoria", 2, "", "2010-08-13"], 51 | ["Victoria", 2, "A House Divided", "2012-02-02"], 52 | ["Victoria", 2, "Heart of Darkness", "2013-04-16"], 53 | ].map do |s,n,d,t| 54 | OpenStruct.new( 55 | series: s, 56 | number: n, 57 | dlc: d, 58 | time: Time.parse(t), 59 | ) 60 | end 61 | -------------------------------------------------------------------------------- /demo/app/data/polish_pms.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | require "time" 3 | 4 | PolishPMs = [ 5 | ["Tadeusz Mazowiecki", "UD", "1989-08-24", "1991-01-04"], 6 | ["Jan Krzysztof Bielecki", "KLD", "1991-01-04", "1991-12-06"], 7 | ["Jan Olszewski", "PC", "1991-12-06", "1992-06-05"], 8 | ["Waldemar Pawlak", "PSL", "1992-06-05", "1992-07-11"], 9 | ["Hanna Suchocka", "UD", "1992-07-11", "1993-10-26"], 10 | ["Waldemar Pawlak", "PSL", "1993-10-26", "1995-03-07"], 11 | ["Józef Oleksy", "SLD", "1995-03-07", "1996-02-07"], 12 | ["Włodzimierz Cimoszewicz", "SLD", "1996-02-07", "1997-10-31"], 13 | ["Jerzy Buzek", "AWS", "1997-10-31", "2001-10-19"], 14 | ["Leszek Miller", "SLD", "2001-10-19", "2004-05-02"], 15 | ["Marek Belka", "SLD", "2004-05-02", "2005-10-31"], 16 | ["Kazimierz Marcinkiewicz", "PiS", "2005-10-31", "2006-07-14"], 17 | ["Jarosław Kaczyński", "PiS", "2006-07-14", "2007-11-16"], 18 | ["Donald Tusk", "PO", "2007-11-16", "2014-09-22"], 19 | ["Ewa Kopacz", "PO", "2014-09-22", "2015-11-16"], 20 | ["Beata Szydło", "PiS", "2015-11-16", "2017-12-11"], 21 | ["Mateusz Morawiecki", "PiS", "2017-12-11", "2018-02-17"], 22 | ].map{|n,p,s,e| 23 | OpenStruct.new( 24 | name: n, 25 | party: p, 26 | start: Time.parse(s), 27 | end: Time.parse(e), 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /demo/app/elections_2016.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/elections_2016" 3 | 4 | list = D3.select("#visualization").append("ul").attr("class", "results") 5 | max_votes = Elections2016.map(&:votes).max 6 | 7 | Elections2016.each do |candidate| 8 | vote_count_formatted = D3.format("3,d").(candidate.votes) 9 | item = list.append("li") 10 | item.append("span") 11 | .attr("class", "vote-bar") 12 | .style("background-color", candidate.color) 13 | .style("width", "#{100.0 * candidate.votes / max_votes}%") 14 | item.append("span") 15 | .attr("class", "vote-description") 16 | .text("#{candidate.name} - #{vote_count_formatted} votes") 17 | end 18 | -------------------------------------------------------------------------------- /demo/app/eu_force_graph.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/eu_countries" 3 | 4 | nodes = EUCountries.map(&:to_n) 5 | links = EUCountriesNeighbours.map(&:to_n) 6 | 7 | svg = D3.select("#visualization") 8 | .append("svg") 9 | .attr("height", "800px") 10 | .attr("width", "800px") 11 | 12 | width = svg.style("width").to_i 13 | height = svg.style("height").to_i 14 | 15 | link_elements = svg.append("g") 16 | .attr("class", "links") 17 | .select_all("line") 18 | .data(links) 19 | .enter().append("line") 20 | .attr("stroke-width", 3) 21 | .attr("stroke", "rgba(50, 50, 50, 0.2)") 22 | 23 | node_elements = svg.append("g") 24 | .attr("class", "nodes") 25 | .select_all("circle") 26 | .data(nodes) 27 | .enter().append("circle") 28 | .attr("r", 10) 29 | .attr("fill", "blue") 30 | 31 | text_elements = svg.append("g") 32 | .attr("class", "texts") 33 | .select_all("text") 34 | .data(nodes) 35 | .enter().append("text") 36 | .text{|node| `node.label` } 37 | .attr("font-size", 15) 38 | .attr("dx", 15) 39 | .attr("dy", 4) 40 | 41 | link_force = D3 42 | .force_link 43 | .id{|link| `link.id` } 44 | .strength{|link| 0.3 } 45 | 46 | simulation = D3 47 | .force_simulation 48 | .force("link", link_force) 49 | .force("charge", D3.force_many_body.strength(-120)) 50 | .force("center", D3.force_center(width / 2, height / 2)) 51 | 52 | simulation.nodes(nodes).on("tick") do 53 | node_elements 54 | .attr("cx"){|node| `node.x`} 55 | .attr("cy"){|node| `node.y`} 56 | text_elements 57 | .attr("x"){|node| `node.x`} 58 | .attr("y"){|node| `node.y`} 59 | link_elements 60 | .attr("x1"){|link| `link.source.x` } 61 | .attr("y1"){|link| `link.source.y` } 62 | .attr("x2"){|link| `link.target.x` } 63 | .attr("y2"){|link| `link.target.y` } 64 | end 65 | 66 | simulation.force("link").links(links) 67 | -------------------------------------------------------------------------------- /demo/app/harry_potter.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/harry_potter" 3 | 4 | svg = D3.select("#visualization") 5 | .append("svg") 6 | .attr("height", "400px") 7 | .attr("width", "100%") 8 | width = svg.style("width").to_i 9 | 10 | bar_size = (400-40-20) / (HarryPotterBooks.size) 11 | 12 | x = D3.scale_linear.domain(HarryPotterBooks.map(&:date).minmax).range([200,width-90]) 13 | y = D3.scale_ordinal.range(HarryPotterBooks.size.times.map{|i| 40+i*bar_size}.reverse) 14 | s = D3.scale_sqrt.domain([0, HarryPotterBooks.map(&:pages).max]).range([0,50]) 15 | 16 | HarryPotterBooks.each do |book| 17 | svg.append("text") 18 | .attr("x", x.(book.date)) 19 | .attr("y", y.(book.title) + bar_size/2) 20 | .attr("font-size", s.(book.pages)) 21 | .text("📖") 22 | .attr("text-anchor", "middle") 23 | .append("title") 24 | .text("#{book.pages} pages") 25 | svg.append("text") 26 | .attr("x", 0) 27 | .attr("y", y.(book.title) + bar_size/2) 28 | .text(book.title) 29 | end 30 | 31 | axis_bottom = D3.axis_bottom(x) 32 | .tick_format(D3.time_format("%Y-%m-%d")) 33 | .tick_values(HarryPotterBooks.map(&:date)) 34 | svg.append("g") 35 | .attr("transform", "translate(0, 380)") 36 | .call(axis_bottom) 37 | -------------------------------------------------------------------------------- /demo/app/iphones.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/iphones" 3 | 4 | svg = D3.select("#visualization") 5 | .append("svg") 6 | .attr("height", "500px") 7 | .attr("width", "100%") 8 | width = svg.style("width").to_i 9 | 10 | x = D3.scale_linear.domain(IPhoneVariants.map(&:released).minmax).range([20,width-90]).nice 11 | y = D3.scale_log.domain(IPhoneVariants.map(&:size).minmax).range([380,20]) 12 | c = D3.scale_ordinal.range(D3.scheme_category_10) 13 | 14 | # If there are multiple points on same date/size combination, 15 | # move any duplicates 15px right (or 30px for triplicates etc.) 16 | duplicates = Hash.new(0) 17 | 18 | graph_area = svg.append("g") 19 | .attr("transform", "translate(60, 20)") 20 | graph_area.select_all("circle") 21 | .data(IPhoneVariants).enter 22 | .append("circle") 23 | .attr("cx"){|d| 24 | count = duplicates[[d.released, d.size]] 25 | duplicates[[d.released, d.size]] += 1 26 | x.(d.released) + 15 * count 27 | } 28 | .attr("cy"){|d| y.(d.size) } 29 | .attr("r", 10) 30 | .attr("fill"){|d| c.(d.name) } 31 | .append("title") 32 | .text{|d| "#{d.name} - #{d.size}GB" } 33 | 34 | axis_left = D3.axis_left(y) 35 | .tick_values([4,8,16,32,64,128,256]) 36 | .tick_format{|v| "#{v} GB" } 37 | graph_area.call(axis_left) 38 | 39 | axis_bottom = D3.axis_bottom(x) 40 | .tick_format(D3.time_format("%Y-%m-%d")) 41 | graph_area.append("g") 42 | .attr("transform", "translate(0, 400)") 43 | .call(axis_bottom) 44 | 45 | D3.select("#visualization") 46 | .append("div") 47 | .style("display", "flex") 48 | .select_all("div") 49 | .data(IPhones).enter 50 | .append("div") 51 | .text{|d| d.name } 52 | .style("background"){|d| c.(d.name) } 53 | .style("padding", "0.5em") 54 | .style("margin", "0.5em") 55 | -------------------------------------------------------------------------------- /demo/app/london_population.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/london_population" 3 | 4 | svg = D3.select("#visualization").append("svg") 5 | 6 | format_tooltip = proc do |d| 7 | "%d - %.1fm / %.1fm" % [d.year, d.inner/1_000_000, d.greater/1_000_000] 8 | end 9 | 10 | svg.append("g").select_all("rect") 11 | .data(LondonPopulation).enter() 12 | .append("rect") 13 | .attr("class", "greater") 14 | .attr("width", 30) 15 | .attr("height"){|d| d.greater/100_000 } 16 | .attr("y"){|d| 200 - d.greater/100_000 } 17 | .attr("x"){|d| (d.year-1800)*4 } 18 | .append("title").text(&format_tooltip) 19 | 20 | svg.append("g").select_all("rect") 21 | .data(LondonPopulation).enter() 22 | .append("rect") 23 | .attr("class", "inner") 24 | .attr("width", 30) 25 | .attr("height"){|d| d.inner/100_000 } 26 | .attr("y"){|d| 200 - d.inner/100_000 } 27 | .attr("x"){|d| (d.year-1800)*4 } 28 | .append("title").text(&format_tooltip) 29 | 30 | svg.append("g").select_all("circle") 31 | .data(LondonPopulation).enter() 32 | .append("circle") 33 | .attr("class", "greater") 34 | .attr("cx"){|d| 15 + (d.year-1800)*4 } 35 | .attr("cy", 300) 36 | .attr("r"){|d| (d.greater**0.5)/150.0 } 37 | .append("title").text(&format_tooltip) 38 | 39 | svg.append("g").select_all("circle") 40 | .data(LondonPopulation).enter() 41 | .append("circle") 42 | .attr("class", "inner") 43 | .attr("cx"){|d| 15 + (d.year-1800)*4 } 44 | .attr("cy", 300) 45 | .attr("r"){|d| (d.inner**0.5)/150.0 } 46 | .append("title").text(&format_tooltip) 47 | 48 | legend = D3.select("#visualization").append("div").attr("id", "legend") 49 | legend.append("tr").append("td").attr("class", "greater").text("Outer London") 50 | legend.append("tr").append("td").attr("class", "inner").text("Inner London") 51 | -------------------------------------------------------------------------------- /demo/app/london_population_area.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/london_population" 3 | 4 | svg = D3.select("#visualization").append("svg") 5 | width = svg.style("width").to_i 6 | 7 | x = D3.scale_linear.domain([1801, 2011]).range([0, width-60]) 8 | y = D3.scale_linear.domain([0, 9_000_000]).range([400, 0]) 9 | 10 | greater_area = D3.area 11 | .x{|d| x.(d.year) } 12 | .y0(400) 13 | .y1{|d| y.(d.greater)} 14 | .curve(D3.curve_natural) 15 | 16 | inner_area = D3.area 17 | .x{|d| x.(d.year) } 18 | .y0(400) 19 | .y1{|d| y.(d.inner)} 20 | .curve(D3.curve_natural) 21 | 22 | graph_area = svg.append("g") 23 | .attr("transform", "translate(60, 20)") 24 | graph_area.append("path") 25 | .attr("d", greater_area.(LondonPopulation)) 26 | .attr("fill", "pink") 27 | graph_area.append("path") 28 | .attr("d", inner_area.(LondonPopulation)) 29 | .attr("fill", "steelblue") 30 | 31 | axis_left = D3.axis_left(y).tick_format{|d| "#{d/1_000_000}M"} 32 | graph_area.call(axis_left) 33 | 34 | axis_bottom = D3.axis_bottom(x).tick_format(D3.format("d")) 35 | graph_area.append("g").attr("transform", "translate(0, 400)").call(axis_bottom) 36 | 37 | legend = D3.select("#visualization").append("div").attr("id", "legend") 38 | legend.append("tr").append("td").attr("class", "greater").text("Outer London") 39 | legend.append("tr").append("td").attr("class", "inner").text("Inner London") 40 | -------------------------------------------------------------------------------- /demo/app/man_vs_horse.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/man_vs_horse" 3 | 4 | svg = D3.select("#visualization") 5 | .append("svg") 6 | .attr("height", "1200px") 7 | .attr("width", "100%") 8 | width = svg.style("width").to_i 9 | 10 | max_time = ManVsHorse.flat_map{|d| [d.horse_time_min, d.human_time_min]}.max 11 | min_year, max_year = ManVsHorse.map(&:year).minmax 12 | 13 | x = D3.scale_linear.domain([0, max_time]).range([0,width-90]).nice 14 | y = D3.scale_linear.domain([min_year, max_year+1]).range([0, 1160]) 15 | c = D3.scale_ordinal.range(D3.scheme_category_10) 16 | 17 | graph_area = svg.append("g") 18 | .attr("transform", "translate(60, 20)") 19 | 20 | graph_area.append("g") 21 | .select_all("rect") 22 | .data(ManVsHorse).enter 23 | .append("rect") 24 | .attr("x"){|d| 0 } 25 | .attr("y"){|d| y.(d.year) } 26 | .attr("width"){|d| x.(d.horse_time_min) } 27 | .attr("height"){|d,i| 0.35 * (y.(i+1) - y.(i)) } 28 | .attr("fill"){|d| c.("horse") } 29 | .append("title") 30 | .text{|d| "🐎 #{d.rider} on #{d.horse} - #{d.horse_time}" } 31 | 32 | graph_area.append("g") 33 | .select_all("rect") 34 | .data(ManVsHorse).enter 35 | .append("rect") 36 | .attr("x"){|d| 0 } 37 | .attr("y"){|d| y.(d.year+0.45) } 38 | .attr("width"){|d| x.(d.human_time_min) } 39 | .attr("height"){|d,i| 0.35 * (y.(i+1) - y.(i)) } 40 | .attr("fill"){|d| c.("man") } 41 | .append("title") 42 | .text{|d| "🏃 #{d.human} - #{d.human_time}" } 43 | 44 | svg.append("g") 45 | .attr("transform", "translate(0, 20)") 46 | .select_all("text") 47 | .data(ManVsHorse).enter 48 | .append("text") 49 | .attr("x"){|d| 0 } 50 | .attr("y"){|d| y.(d.year+0.5) } 51 | .text{|d| (d.winner == "Horse" ? "🐎" : "🏃") + " #{d.year}" } 52 | 53 | axis_bottom = D3.axis_bottom(x) 54 | .tick_format{|v| h = (v / 60).floor; m = (v % 60).floor; "%d:%02d" % [h,m] } 55 | graph_area.append("g").attr("transform", "translate(0, 1160)").call(axis_bottom) 56 | -------------------------------------------------------------------------------- /demo/app/mtg_modern_colors.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/mtg_modern_colors" 3 | 4 | flex = D3.select("#visualization") 5 | .append("div") 6 | .style("display", "flex") 7 | .style("flex-wrap", "wrap") 8 | 9 | MtgModernColors.group_by(&:cmc).sort.each do |cmc, cards| 10 | svg = flex.append("svg") 11 | .attr("height", "200px") 12 | .attr("width", "200px") 13 | graph_area = svg 14 | .append("g") 15 | .attr("transform", "translate(100,100)") 16 | graph_area.append("text") 17 | .attr("text-anchor", "middle") 18 | .attr("y", 10) 19 | .text(cmc) 20 | .attr("font-size", "30px") 21 | 22 | cards.sort_by!{|c| "wubrgxm".index(c.color)} 23 | pie = D3.pie.value(&:count).sort(nil) 24 | 25 | pie.(cards).each do |arc_data| 26 | arc = D3.arc 27 | .inner_radius(40) 28 | .outer_radius(90) 29 | .start_angle(arc_data[:start_angle]) 30 | .end_angle(arc_data[:end_angle]) 31 | 32 | color = { 33 | w: "white", 34 | u: "blue", 35 | b: "black", 36 | r: "red", 37 | g: "green", 38 | x: "grey", 39 | m: "yellow", 40 | }[arc_data[:data].color] 41 | 42 | count = arc_data[:data].count 43 | label = "#{count} #{(count == 1) ? 'card' : 'cards'}" 44 | 45 | graph_area.append("path") 46 | .attr("d", arc.()) 47 | .attr("fill", color) 48 | .attr("stroke", "black") 49 | .attr("stroke-width", "1px") 50 | .append("title") 51 | .text(label) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /demo/app/mtg_modern_creatures.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/mtg_modern_creatures" 3 | 4 | svg = D3.select("#visualization") 5 | .append("svg") 6 | .attr("height", "600px") 7 | .attr("width", "600px") 8 | 9 | # Use one scale not two so it's a square 10 | min, max = MtgModernCreatures.flat_map{|c| [c.power, c.toughness]}.minmax 11 | 12 | pt = D3.scale_linear.domain([min-1, max+1]).range([0, 580]) 13 | box_size = pt.(1) - pt.(0) 14 | c = D3.scale_log 15 | .domain([1, MtgModernCreatures.map(&:count).max]) 16 | .range(["rgb(128, 255, 128)", "rgb(128, 128, 255)"]) 17 | 18 | graph_area = svg.append("g").attr("transform", "translate(0,0)") 19 | 20 | MtgModernCreatures.each do |cards| 21 | graph_area.append("rect") 22 | .attr("x", pt.(cards.toughness)) 23 | .attr("y", pt.(cards.power)) 24 | .attr("height", box_size) 25 | .attr("width", box_size) 26 | .attr("fill", c.(cards.count)) 27 | graph_area.append("text") 28 | .attr("x", pt.(cards.toughness) + box_size/2) 29 | .attr("y", pt.(cards.power) + box_size/2 + 5) 30 | .attr("text-anchor", "middle") 31 | .text(cards.count) 32 | end 33 | 34 | (min..max).each do |v| 35 | graph_area.append("text") 36 | .attr("x", pt.(v) + box_size/2) 37 | .attr("y", pt.(min-1) + box_size/2 + 5) 38 | .attr("text-anchor", "middle") 39 | .text(v) 40 | 41 | graph_area.append("text") 42 | .attr("y", pt.(v) + box_size/2 + 5) 43 | .attr("x", pt.(min-1) + box_size/2) 44 | .attr("text-anchor", "middle") 45 | .text(v) 46 | end 47 | 48 | (min..max+1).each do |v| 49 | graph_area.append("line") 50 | .attr("x1", pt.(min)) 51 | .attr("x2", pt.(max+1)) 52 | .attr("y1", pt.(v)) 53 | .attr("y2", pt.(v)) 54 | .attr("stroke", "black") 55 | 56 | graph_area.append("line") 57 | .attr("y1", pt.(min)) 58 | .attr("y2", pt.(max+1)) 59 | .attr("x1", pt.(v)) 60 | .attr("x2", pt.(v)) 61 | .attr("stroke", "black") 62 | end 63 | -------------------------------------------------------------------------------- /demo/app/olympics_2016_medals.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/olympics_2016_medals" 3 | 4 | height = Olympics2016Medals.size * 24 5 | svg = D3.select("#visualization") 6 | .append("svg") 7 | .attr("height", "#{height}px") 8 | .attr("width", "100%") 9 | 10 | width = svg.style("width").to_i 11 | max_medals = Olympics2016Medals.map(&:total).max 12 | 13 | x = D3.scale_linear.domain([0, max_medals]).range([0, width-50]) 14 | y = D3.scale_linear.domain([0, Olympics2016Medals.size]).range([0, height]) 15 | 16 | chart_area = svg.append("g").attr("transform", "translate(50, 0)") 17 | 18 | medal = chart_area.append("g") 19 | .select_all("g") 20 | .data(Olympics2016Medals) 21 | .enter.append("g") 22 | 23 | medal.append("rect") 24 | .attr("height", 20) 25 | .attr("width"){|d| x.(d.gold) } 26 | .attr("x", 0) 27 | .attr("y"){|d,i| y.(i) } 28 | .attr("fill", "gold") 29 | 30 | medal.append("text") 31 | .text{|d| d.gold == 0 ? "" : d.gold } 32 | .attr("x"){|d| x.(d.gold/2) } 33 | .attr("y"){|d,i| y.(i+0.7) } 34 | .style("text-anchor", "middle") 35 | 36 | medal.append("rect") 37 | .attr("height", 20) 38 | .attr("width"){|d| x.(d.silver)} 39 | .attr("x"){|d| x.(d.gold) } 40 | .attr("y"){|d,i| y.(i) } 41 | .attr("fill", "silver") 42 | 43 | medal.append("text") 44 | .text{|d| d.silver == 0 ? "" : d.silver } 45 | .attr("x"){|d| x.(d.gold + d.silver/2) } 46 | .attr("y"){|d,i| y.(i+0.7) } 47 | .style("text-anchor", "middle") 48 | 49 | medal.append("rect") 50 | .attr("height", 20) 51 | .attr("width"){|d| x.(d.bronze) } 52 | .attr("x"){|d| x.(d.gold + d.silver) } 53 | .attr("y"){|d,i| y.(i) } 54 | .attr("fill", "#CD7F32") 55 | 56 | medal.append("text") 57 | .text{|d| d.bronze == 0 ? "" : d.bronze} 58 | .attr("x"){|d| x.(d.gold + d.silver + d.bronze/2) } 59 | .attr("y"){|d,i| y.(i+0.7) } 60 | .style("text-anchor", "middle") 61 | 62 | medal.append("text") 63 | .attr("x", -50) 64 | .attr("y"){|d,i| y.(i+0.7) } 65 | .text{|d| d.acronym } 66 | .style("font-size", "20px") 67 | -------------------------------------------------------------------------------- /demo/app/paradox.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/paradox" 3 | 4 | svg = D3.select("#visualization") 5 | .append("svg") 6 | .attr("height", "600px") 7 | .attr("width", "100%") 8 | width = svg.style("width").to_i 9 | 10 | min_date, max_date = ParadoxGames.map(&:time).minmax 11 | count = ParadoxGames.map{|g| [g.series, g.number]}.uniq.size 12 | bar_height = 580 / count 13 | 14 | x = D3.scale_linear.domain([min_date, max_date]).range([0,width-220]) 15 | y = D3.scale_ordinal.range(count.times.map{|i| bar_height * i}) 16 | c = D3.scale_ordinal.range(D3.scheme_category_10) 17 | stripes = D3.scale_ordinal.range([0,1]) 18 | 19 | graph_area = svg.append("g") 20 | .attr("transform", "translate(200, 20)") 21 | 22 | ParadoxGames.each do |game| 23 | dlc = (game.dlc != "") 24 | full_game = "#{game.series} #{game.number}" 25 | 26 | unless dlc 27 | graph_area.append("text") 28 | .attr("x", -200 + 5) 29 | .attr("y", y.(full_game) + 4) 30 | .text(full_game) 31 | 32 | color = D3.color(c.("#{game.series}")) 33 | color = color.brighter(stripes.("#{full_game}")) 34 | graph_area.append("rect") 35 | .attr("x", -200) 36 | .attr("width", width) 37 | .attr("y", y.(full_game) - bar_height/2) 38 | .attr("height", bar_height) 39 | .attr("fill", color) 40 | .attr("opacity", 0.2) 41 | end 42 | 43 | graph_area.append("circle") 44 | .attr("cx", x.(game.time)) 45 | .attr("cy", y.(full_game)) 46 | .attr("r", dlc ? 8 : 12) 47 | .attr("fill", c.("#{game.series}")) 48 | .attr("opacity", 0.6) 49 | .attr("stroke-width", "1px") 50 | .attr("stroke", "black") 51 | .append("title") 52 | .text("#{dlc ? game.dlc : full_game} - #{D3.time_format("%B %Y").(game.time)}") 53 | end 54 | 55 | 56 | axis_bottom = D3.axis_bottom(x) 57 | .tick_format(D3.time_format("%B %Y")) 58 | graph_area.append("g").attr("transform", "translate(0, 560)").call(axis_bottom) 59 | -------------------------------------------------------------------------------- /demo/app/polish_pms.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/polish_pms" 3 | 4 | svg = D3.select("#visualization") 5 | .append("svg") 6 | .attr("height", "600px") 7 | .attr("width", "100%") 8 | width = svg.style("width").to_i 9 | 10 | min_date = PolishPMs.map(&:start).min 11 | max_date = PolishPMs.map(&:end).max 12 | 13 | x = D3.scale_linear.domain([min_date, max_date]).range([100, width-90]) 14 | y = D3.scale_linear.domain([0, PolishPMs.size+1]).range([0, 580]) 15 | c = D3.scale_ordinal.range(D3.scheme_category_10) 16 | 17 | graph_area = svg.append("g") 18 | .attr("transform", "translate(60, 20)") 19 | 20 | graph_area.append("g") 21 | .select_all("rect") 22 | .data(PolishPMs).enter 23 | .append("rect") 24 | .attr("x"){|d| x.(d.start) } 25 | .attr("y"){|d,i| y.(i) } 26 | .attr("width"){|d| x.(d.end) - x.(d.start) } 27 | .attr("height"){|d,i| 0.8 * (y.(i+1) - y.(i)) } 28 | .attr("fill"){|d| c.(d.party) } 29 | 30 | graph_area.append("g") 31 | .select_all("text") 32 | .data(PolishPMs).enter 33 | .append("text") 34 | .text{|d| d.name} 35 | .attr("x"){|d| x.(d.start) - 4 } 36 | .attr("y"){|d,i| y.(i+0.5) } 37 | .attr("text-anchor", "end") 38 | .style("font-size", "12px") 39 | 40 | graph_area.append("g") 41 | .select_all("text") 42 | .data(PolishPMs).enter 43 | .append("text") 44 | .text{|d| d.party } 45 | .attr("x"){|d| x.(d.start) + 4 } 46 | .attr("y"){|d,i| y.(i+0.5) } 47 | .attr("text-anchor", "begin") 48 | .style("font-size", "12px") 49 | 50 | axis_bottom = D3.axis_bottom(x) 51 | .tick_format(D3.time_format("%B %Y")) 52 | graph_area.append("g") 53 | .attr("transform", "translate(0, 560)") 54 | .call(axis_bottom) 55 | -------------------------------------------------------------------------------- /demo/app/star_trek_voyager.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/star_trek_voyager" 3 | 4 | svg = D3.select("#visualization") 5 | .append("svg") 6 | .attr("height", "600px") 7 | .attr("width", "100%") 8 | width = svg.style("width").to_i 9 | 10 | x = D3.scale_linear.domain([0, StarTrekVoyager.size-1]).range([40, width-20]) 11 | y = D3.scale_linear.domain(StarTrekVoyager.map(&:rating).minmax).range([550, 50]) 12 | c = D3.scale_ordinal.range(D3.scheme_category_10) 13 | 14 | (1..7).each do |season| 15 | episodes = StarTrekVoyager.select{|episode| episode.season == season } 16 | avg = D3.mean(episodes.map(&:rating)) 17 | svg.append("line") 18 | .attr("x1", x.(episodes.map(&:number).min)) 19 | .attr("x2", x.(episodes.map(&:number).max)) 20 | .attr("y1", y.(avg)) 21 | .attr("y2", y.(avg)) 22 | .attr("stroke", c.(season)) 23 | .attr("stroke-width", 2) 24 | .attr("opacity", 0.4) 25 | end 26 | 27 | StarTrekVoyager.each do |episode| 28 | svg.append("circle") 29 | .attr("cx", x.(episode.number)) 30 | .attr("cy", y.(episode.rating)) 31 | .attr("r", 4) 32 | .attr("fill", c.(episode.season)) 33 | .attr("opacity", 0.6) 34 | .append("title") 35 | .text("S%02dE%02d %s" % [episode.season, episode.number, episode.title]) 36 | end 37 | 38 | axis = D3.axis_left(y) 39 | svg.append("g").attr("transform","translate(30, 0)") 40 | .call(axis) 41 | -------------------------------------------------------------------------------- /demo/app/temperature.rb: -------------------------------------------------------------------------------- 1 | # Inspired by freecodecamp project 2 | # This visualization really stretches what opal-d3 can currently comfortably do 3 | # The code will hopefully become nicer in future versions of the gem 4 | require "opal-d3" 5 | require "json" 6 | 7 | url = "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/global-temperature.json" 8 | D3.json(url) do |err, response| 9 | response = JSON.parse(`JSON.stringify(response)`) 10 | base_temperature = response[:baseTemperature] 11 | data_points = response[:monthlyVariance] 12 | 13 | visualization = D3.select("#visualization") 14 | svg = visualization.append("svg") 15 | height = svg.style("height").to_i 16 | width = svg.style("width").to_i 17 | margin_left = 100 18 | margin_right = 50 19 | margin_top = 150 20 | margin_bottom = 150 21 | 22 | tooltip = visualization.append("div").attr("id", "tooltip") 23 | 24 | xmin, xmax = D3.extent(data_points){|d| d[:year] } 25 | tmin, tmax = D3.extent(data_points){|d| d[:variance] } 26 | 27 | xscale = D3.scale_linear 28 | .domain([xmin-0.5 , xmax+0.5]) 29 | .range([margin_left, width-margin_right]) 30 | yscale = D3.scale_linear 31 | .domain([0.5, 12+0.5]) 32 | .range([margin_bottom, height-margin_top]) 33 | 34 | tdomain = [tmin, tmin/2, 0, tmax/2, tmax] 35 | tscale = D3.scale_linear 36 | .range(["blue", "cyan", "green", "yellow", "red"]) 37 | .domain(tdomain) 38 | .interpolate(&D3.interpolate_hcl) 39 | 40 | month_names = [ 41 | "January", "February", "March", 42 | "April", "May", "June", 43 | "July", "August", "September", 44 | "October", "November", "December", 45 | ] 46 | 47 | xaxis = D3.axis_bottom(xscale) 48 | .tick_format(&D3.format("d")) 49 | svg.append("g").attr("id", "x-axis") 50 | .style("transform", "translate(0, #{height-margin_bottom}px)") 51 | .call(xaxis) 52 | 53 | yaxis = D3.axis_left(yscale) 54 | .tick_format{|d| month_names[d-1] } 55 | svg.append("g").attr("id", "y-axis") 56 | .style("transform", "translate(#{margin_left}px, 0)") 57 | .call(yaxis) 58 | 59 | svg.append("text").text("Historical Temperature Record") 60 | .attr("x", "#{width/2}px") 61 | .attr("y", "50px") 62 | .attr("id", "title") 63 | 64 | svg.append("text").text("#{xmin} - #{xmax}") 65 | .attr("x", "#{width/2}px") 66 | .attr("y", "100px") 67 | .attr("id", "description") 68 | 69 | legendscale = D3.scale_linear 70 | .domain([tmin, tmin/2, 0, tmax/2, tmax]) 71 | .range([0, 40, 80, 120, 160]) 72 | 73 | svg.append("g") 74 | .attr("id", "legend") 75 | .select_all("rect") 76 | .data(tdomain) 77 | .enter 78 | .append("rect") 79 | .attr("x"){|d,i| 100+40*i } 80 | .attr("y", height - 100) 81 | .attr("width", "40px") 82 | .attr("height", "40px") 83 | .style("border", "1px solid black") 84 | .style("fill"){|d| tscale.(d) } 85 | 86 | legendaxis = D3.axis_bottom(legendscale) 87 | 88 | svg.append("g") 89 | .style("transform", "translate(120px, #{height-50}px)") 90 | .call(legendaxis) 91 | 92 | svg.select_all("rect") 93 | .data(data_points) 94 | .enter 95 | .append("rect") 96 | .attr("class", "cell") 97 | .attr("x"){|d| xscale.(d[:year]-0.5) } 98 | .attr("y"){|d| yscale.(d[:month]-0.5) } 99 | .attr("height"){|d| yscale.(d[:month]+0.5) - yscale.(d[:month]-0.5) } 100 | .attr("width"){|d| xscale.(d[:year]+0.5) - xscale.(d[:year]-0.5) } 101 | .style("fill"){|d| tscale.(d[:variance]) } 102 | .on("mouseover"){|d| 103 | tooltip 104 | .style("display", "block") 105 | .style("left", "#{xscale.(d[:year])}px") 106 | .style("top", "#{yscale.(d[:month])}px") 107 | .html("#{month_names[d[:month]-1]} #{d[:year]}
#{d[:variance]}") 108 | } 109 | .on("mouseout"){|d| 110 | tooltip 111 | .style("display", "none") 112 | } 113 | end 114 | -------------------------------------------------------------------------------- /demo/app/us_gdp.rb: -------------------------------------------------------------------------------- 1 | # Inspired by freecodecamp project 2 | # This visualization really stretches what opal-d3 can currently comfortably do 3 | # The code will hopefully become nicer in future versions of the gem 4 | require "opal-d3" 5 | require "json" 6 | require "time" 7 | 8 | url = "https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json" 9 | 10 | D3.json(url) do |error, response| 11 | # This interface could surely be better 12 | response = JSON.parse(`JSON.stringify(response)`) 13 | data = response["data"] 14 | 15 | visualization = D3.select("#visualization") 16 | svg = visualization.append("svg") 17 | width = svg.style("width").to_i 18 | 19 | xdomain = D3.extent(data){|x,y| Time.parse(x) } 20 | ydomain = [0, D3.max(data){|x,y| y }] 21 | 22 | xscale = D3.scale_time.domain(xdomain).range([50, width-10]) 23 | yscale = D3.scale_linear.domain(ydomain).range([450, 10]) 24 | xaxis = D3.axis_bottom(xscale) 25 | yaxis = D3.axis_left(yscale) 26 | 27 | tooltip = visualization.append("div").attr("id", "tooltip") 28 | .style("opacity", 0) 29 | 30 | D3.select("svg").append("g").attr("id","x-axis").call(xaxis) 31 | D3.select("svg").append("g").attr("id","y-axis").call(yaxis) 32 | 33 | # This array should get automatically unpacked 34 | D3.select("svg").select_all("rect") 35 | .data(data).enter 36 | .append("rect").attr("class", "bar") 37 | .attr("x"){|(x,y)| xscale.(Time.parse(x)) } 38 | .attr("y"){|(x,y)| yscale.(y) } 39 | .attr("width", 2) 40 | .attr("height"){|(x,y)| 450 - yscale.(y) } 41 | .on("mouseover"){|(x,y)| 42 | tooltip 43 | .style("opacity", 0.8) 44 | tooltip.html("#{x}
#{y}") 45 | .style("left", "#{`d3.event.pageX`}px") 46 | .style("top", "#{`d3.event.pageY` - 28}px") 47 | } 48 | .on("mouseout"){ 49 | # This could use .transition.duration(500), but opal-d3 doesn't support that yet 50 | tooltip 51 | .style("opacity", 0) 52 | } 53 | end 54 | -------------------------------------------------------------------------------- /demo/app/weather_in_london.rb: -------------------------------------------------------------------------------- 1 | require "opal-d3" 2 | require "data/weather_in_london" 3 | 4 | row = D3.select("#visualization").append("div").attr("class", "row") 5 | 6 | min_temp = WeatherInLondon.map(&:min).min 7 | max_temp = WeatherInLondon.map(&:max).max 8 | 9 | x = D3.scale_linear.domain([1,31]).range([30, 190]) 10 | y = D3.scale_linear.domain([min_temp, max_temp]).range([180, 20]) 11 | 12 | (1..12).each do |month| 13 | header = D3.time_format("%B").(Time.parse("2016-#{month}-01")) 14 | panel = row.append("div").attr("class", "col-xs-6 col-sm-4") 15 | panel.append("h3").text(header) 16 | svg = panel.append("svg") 17 | .attr("height", "200px") 18 | .attr("width", "200px") 19 | 20 | svg.append("rect") 21 | .attr("x", 20) 22 | .attr("y", 10) 23 | .attr("width", 180) 24 | .attr("height", 180) 25 | .attr("fill", "lightgrey") 26 | 27 | data = WeatherInLondon.select{|row_data| row_data.month == month} 28 | 29 | max_temp = D3.line 30 | .x{|d| x.(d.day) } 31 | .y{|d| y.(d.max) } 32 | .curve(D3.curve_natural) 33 | svg.append("path") 34 | .attr("d", max_temp.(data)) 35 | .attr("stroke", "red") 36 | .attr("stroke-width", "2px") 37 | .attr("fill", "none") 38 | 39 | mean_temp = D3.line 40 | .x{|d| x.(d.day) } 41 | .y{|d| y.(d.mean) } 42 | .curve(D3.curve_natural) 43 | svg.append("path") 44 | .attr("d", mean_temp.(data)) 45 | .attr("stroke", "black") 46 | .attr("stroke-width", "2px") 47 | .attr("fill", "none") 48 | 49 | min_temp = D3.line 50 | .x{|d| x.(d.day) } 51 | .y{|d| y.(d.min) } 52 | .curve(D3.curve_natural) 53 | svg.append("path") 54 | .attr("d", min_temp.(data)) 55 | .attr("stroke", "blue") 56 | .attr("stroke-width", "2px") 57 | .attr("fill", "none") 58 | 59 | axis_left = D3.axis_left(y).tick_size(2) 60 | svg.append("g").attr("transform", "translate(20,0)").call(axis_left) 61 | end 62 | -------------------------------------------------------------------------------- /demo/assets/stylesheets/cycling.css: -------------------------------------------------------------------------------- 1 | #visualization { 2 | position: relative; 3 | } 4 | svg { 5 | width: 100%; 6 | height: 500px; 7 | background-color: #f0f0f0; 8 | } 9 | .dot { 10 | fill: blue; 11 | } 12 | .dot.doping { 13 | fill: red; 14 | } 15 | #tooltip { 16 | display: none; 17 | position: absolute; 18 | background: white; 19 | } 20 | -------------------------------------------------------------------------------- /demo/assets/stylesheets/elections_2016.css: -------------------------------------------------------------------------------- 1 | .results li { 2 | position: realtive; 3 | } 4 | .vote-bar { 5 | opacity: 0.5; 6 | position: absolute; 7 | height: 100%; 8 | } 9 | .vote-description { 10 | position: absolute; 11 | } 12 | -------------------------------------------------------------------------------- /demo/assets/stylesheets/london_population.css: -------------------------------------------------------------------------------- 1 | svg { 2 | height: 400px; 3 | width: 100%; 4 | } 5 | .greater { 6 | fill: pink; 7 | } 8 | .inner { 9 | fill: steelblue; 10 | } 11 | .greater:hover, .inner:hover { 12 | stroke-width: 1px; 13 | stroke: black; 14 | } 15 | #legend .greater { 16 | background-color: pink; 17 | } 18 | #legend .inner { 19 | background-color: steelblue; 20 | } 21 | #legend td { 22 | padding: 0.5em; 23 | } 24 | -------------------------------------------------------------------------------- /demo/assets/stylesheets/london_population_area.css: -------------------------------------------------------------------------------- 1 | svg { 2 | height: 500px; 3 | width: 100%; 4 | } 5 | #legend .greater { 6 | background-color: pink; 7 | } 8 | #legend .inner { 9 | background-color: steelblue; 10 | } 11 | #legend td { 12 | padding: 0.5em; 13 | } 14 | -------------------------------------------------------------------------------- /demo/assets/stylesheets/temperature.css: -------------------------------------------------------------------------------- 1 | #visualization { 2 | position: relative; 3 | } 4 | #title { 5 | text-anchor: middle; 6 | font-size: 32px; 7 | } 8 | svg { 9 | height: 700px; 10 | width: 100%; 11 | background-color: #f0f0f0; 12 | } 13 | #tooltip { 14 | position: absolute; 15 | display: none; 16 | background-color: white; 17 | opacity: 0.5; 18 | text-align: center; 19 | } 20 | -------------------------------------------------------------------------------- /demo/assets/stylesheets/us_gdp.css: -------------------------------------------------------------------------------- 1 | svg { 2 | height: 500px; 3 | width: 100%; 4 | background-color: #ddd; 5 | } 6 | #x-axis { 7 | transform: translate(0px, 450px); 8 | } 9 | #y-axis { 10 | transform: translate(50px, 0px); 11 | } 12 | rect:hover { 13 | fill: red; 14 | } 15 | #tooltip { 16 | position: absolute; 17 | text-align: center; 18 | background-color: #ccf; 19 | width: 100px; 20 | height: 52px; 21 | pointer-events: none; 22 | } 23 | -------------------------------------------------------------------------------- /demo/config.ru: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler.require 3 | 4 | opal = Opal::Server.new do |s| 5 | s.append_path "app" 6 | s.append_path "assets" 7 | end 8 | 9 | map "/assets" do 10 | run opal.sprockets 11 | end 12 | 13 | visualizations = { 14 | elections_2016: "Elections 2016", 15 | london_population: "London Population", 16 | london_population_area: "London Population - Area Chart", 17 | olympics_2016_medals: "Olympics 2016 Medals", 18 | iphones: "iPhones", 19 | polish_pms: "Polish Prime Ministers", 20 | man_vs_horse: "Man versus Horse Marathon", 21 | paradox: "Paradox Interactive Games", 22 | weather_in_london: "Weather in London", 23 | harry_potter: "Harry Potter Books", 24 | mtg_modern_creatures: "MTG: Creatures in Modern", 25 | mtg_modern_colors: "MTG: Spell Cards in Modern", 26 | star_trek_voyager: "Star Trek: Voyager", 27 | us_gdp: "US GDP", 28 | cycling: "Doping in Professional Bicycle Racing", 29 | temperature: "Historical Temperature Record", 30 | eu_force_graph: "EU Countries Force Graph", 31 | } 32 | 33 | visualizations.each do |script, title| 34 | get "/v/#{script}.html" do 35 | erb :visualization, {}, {script: script, title: title} 36 | end 37 | end 38 | 39 | get "/" do 40 | erb :index, {}, {visualizations: visualizations} 41 | end 42 | 43 | run Sinatra::Application 44 | -------------------------------------------------------------------------------- /demo/views/index.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demos 6 | 7 | 8 | 9 |
10 |

opal-d3 demos

11 | 16 |
17 | opal-d3 source code 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/views/visualization.erb: -------------------------------------------------------------------------------- 1 | <% 2 | stylesheet_path = "#{__dir__}/../assets/stylesheets/#{script}.css" 3 | code_path = "#{__dir__}/../app/#{script}.rb" 4 | %> 5 | 6 | 7 | 8 | <%= title %> 9 | 10 | 11 | <% if File.exist?(stylesheet_path) %> 12 | 13 | <% end %> 14 | 15 | 16 | 17 | 18 |
19 |

<%= title %>

20 |
21 |
22 |

Code

23 |
<%= open(code_path).read.gsub("<", "<") %>
24 |
25 | <% if File.exist?(stylesheet_path) %> 26 |
27 |

Stylesheet

28 |
<%= open(stylesheet_path).read.gsub("<", "<") %>
29 |
30 | <% end %> 31 | 32 | 33 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /lib/opal-d3.rb: -------------------------------------------------------------------------------- 1 | if RUBY_ENGINE == "opal" 2 | require "opal" 3 | require "active_support/core_ext/string" 4 | require_relative "opal/d3" 5 | else 6 | require "opal" 7 | require "opal-activesupport" 8 | require_relative "opal/d3/version" 9 | 10 | # This seems to be needed by sprockets somehow 11 | Opal.append_path __dir__.untaint 12 | end 13 | -------------------------------------------------------------------------------- /lib/opal/d3.rb: -------------------------------------------------------------------------------- 1 | require "opal" 2 | 3 | class Module 4 | def alias_d3(ruby_name=nil, js_name) 5 | ruby_name ||= js_name.underscore 6 | define_method(ruby_name) do |*args| 7 | @d3.JS[js_name].JS.apply(@d3, `Opal.to_a(args)`) 8 | end 9 | end 10 | end 11 | 12 | module D3 13 | @d3 = `window.d3` 14 | class << self 15 | end 16 | end 17 | # Metaclass: 18 | require_relative "d3/native" 19 | 20 | # Everything else: 21 | require_relative "d3/arc" 22 | require_relative "d3/area" 23 | require_relative "d3/axis" 24 | require_relative "d3/band_scale" 25 | require_relative "d3/collections" 26 | require_relative "d3/color" 27 | require_relative "d3/continuous_scale" 28 | require_relative "d3/creator" 29 | require_relative "d3/curve" 30 | require_relative "d3/dsv" 31 | require_relative "d3/ease" 32 | require_relative "d3/force" 33 | require_relative "d3/format" 34 | require_relative "d3/histograms" 35 | require_relative "d3/interpolate" 36 | require_relative "d3/line" 37 | require_relative "d3/map" 38 | require_relative "d3/misc" 39 | require_relative "d3/nest" 40 | require_relative "d3/ordinal_scale" 41 | require_relative "d3/path" 42 | require_relative "d3/pie" 43 | require_relative "d3/point_scale" 44 | require_relative "d3/polygon" 45 | require_relative "d3/quadtree" 46 | require_relative "d3/quantile_scale" 47 | require_relative "d3/quantize_scale" 48 | require_relative "d3/radial_area" 49 | require_relative "d3/radial_line" 50 | require_relative "d3/random" 51 | require_relative "d3/request" 52 | require_relative "d3/search" 53 | require_relative "d3/selection" 54 | require_relative "d3/sequential_scale" 55 | require_relative "d3/set" 56 | require_relative "d3/stack" 57 | require_relative "d3/statistics" 58 | require_relative "d3/symbol" 59 | require_relative "d3/threshold_scale" 60 | require_relative "d3/time_format" 61 | require_relative "d3/time_interval" 62 | require_relative "d3/transformations" 63 | -------------------------------------------------------------------------------- /lib/opal/d3/arc.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class ArcGenerator 3 | include D3::Native 4 | def call(*args) 5 | @native.call(*args) 6 | end 7 | 8 | attribute_d3_block :innerRadius 9 | attribute_d3_block :outerRadius 10 | attribute_d3_block :cornerRadius 11 | attribute_d3_block :startAngle 12 | attribute_d3_block :endAngle 13 | alias_native :centroid 14 | end 15 | 16 | class << self 17 | def arc 18 | D3::ArcGenerator.new @d3.JS.arc 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/opal/d3/area.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class AreaGenerator 3 | include D3::Native 4 | 5 | def call(*args) 6 | result = @native.call(*args) 7 | `result === null ? nil : result` 8 | end 9 | 10 | attribute_d3_block :x 11 | attribute_d3_block :x0 12 | attribute_d3_block :x1 13 | attribute_d3_block :y 14 | attribute_d3_block :y0 15 | attribute_d3_block :y1 16 | attribute_d3_block :defined 17 | 18 | def curve(new_value=`undefined`) 19 | if `new_value === undefined` 20 | D3::Curve.new @native.JS.curve 21 | else 22 | @native.JS.curve(new_value.to_n) 23 | self 24 | end 25 | end 26 | 27 | def line_x0 28 | D3::LineGenerator.new @native.JS.lineX0 29 | end 30 | 31 | def line_x1 32 | D3::LineGenerator.new @native.JS.lineX1 33 | end 34 | 35 | def line_y0 36 | D3::LineGenerator.new @native.JS.lineY0 37 | end 38 | 39 | def line_y1 40 | D3::LineGenerator.new @native.JS.lineY1 41 | end 42 | end 43 | 44 | class << self 45 | def area 46 | D3::AreaGenerator.new @d3.JS.area 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/opal/d3/axis.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | # It might be better to rewrap the Scale every time, as we're duplicating it 3 | class Axis 4 | include D3::Native 5 | 6 | def initialize(native, scale_obj) 7 | raise unless native 8 | raise unless scale_obj 9 | @scale_obj = scale_obj 10 | @native = native 11 | end 12 | 13 | attribute_d3 :tickSizeInner 14 | attribute_d3 :tickSizeOuter 15 | attribute_d3 :tickSize 16 | attribute_d3 :tickPadding 17 | attribute_d3 :tickArguments 18 | alias_native_chainable :ticks 19 | 20 | def call(context) 21 | @native.call(context.to_n) 22 | self 23 | end 24 | 25 | def scale(v=`undefined`) 26 | if `v === undefined` 27 | @scale_obj 28 | else 29 | @scale_obj = v 30 | @native.JS.scale(v.to_n) 31 | self 32 | end 33 | end 34 | alias_method :scale=, :scale 35 | 36 | def tick_values(v=`undefined`) 37 | if `v === undefined` 38 | result = @native.JS.tickValues 39 | `result === null ? nil : result` 40 | else 41 | @native.JS.tickValues(v == nil ? `null` : v) 42 | self 43 | end 44 | end 45 | alias_method :tick_values=, :tick_values 46 | 47 | def tick_format(v=`undefined`, &block) 48 | v = block if block_given? 49 | if `v === undefined` 50 | result = @native.JS.tickFormat 51 | `result === null ? nil : result` 52 | else 53 | @native.JS.tickFormat(v == nil ? `null` : v) 54 | self 55 | end 56 | end 57 | alias_method :tick_format=, :tick_format 58 | end 59 | 60 | class << self 61 | def axis_top(scale) 62 | D3::Axis.new(@d3.JS.axisTop(scale.to_n), scale) 63 | end 64 | 65 | def axis_bottom(scale) 66 | D3::Axis.new(@d3.JS.axisBottom(scale.to_n), scale) 67 | end 68 | 69 | def axis_right(scale) 70 | D3::Axis.new(@d3.JS.axisRight(scale.to_n), scale) 71 | end 72 | 73 | def axis_left(scale) 74 | D3::Axis.new(@d3.JS.axisLeft(scale.to_n), scale) 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/opal/d3/band_scale.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class BandScale 3 | include D3::Native 4 | def call(t) 5 | v = @native.call(t) 6 | `v === undefined ? nil : v` 7 | end 8 | attribute_d3 :domain 9 | attribute_d3 :range 10 | alias_native_new :copy 11 | alias_native :bandwidth 12 | alias_native :step 13 | # this is really weirdo one, as it sets both paddings* but returns inner one 14 | # All need to be in [0,1] range 15 | attribute_d3 :padding 16 | attribute_d3 :paddingInner 17 | attribute_d3 :paddingOuter 18 | attribute_d3 :align 19 | attribute_d3 :round 20 | # This requires argument, we might redo this not to 21 | alias_native_chainable :rangeRound 22 | end 23 | 24 | class << self 25 | def scale_band(*args) 26 | D3::BandScale.new @d3.JS.scaleBand(*args) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/opal/d3/collections.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class << self 3 | alias_d3 :keys 4 | alias_d3 :values 5 | 6 | def entries(obj) 7 | @d3.JS.entries(obj).map{|o| [`o.key`, `o.value`]} 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/opal/d3/color.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class Color 3 | include D3::Native 4 | attr_reader :native 5 | alias_native :to_s, :toString 6 | alias_native_new :brighter 7 | alias_native_new :darker 8 | alias_native :displayable?, :displayable 9 | alias_native_new :rgb 10 | 11 | # Various subsets of these are valid depending on color - maybe we should properly subclass? 12 | def a; `#@native.a` end 13 | def b; `#@native.b` end 14 | def c; `#@native.c` end 15 | def g; `#@native.g` end 16 | def h; `#@native.h` end 17 | def l; `#@native.l` end 18 | def r; `#@native.r` end 19 | def s; `#@native.s` end 20 | def opacity; `#@native.opacity` end 21 | end 22 | 23 | class <" 38 | end 39 | end 40 | 41 | class << self 42 | def map(object=nil, &block) 43 | if block_given? 44 | D3::Map.new(@d3.JS.map(object, proc{|x| yield(x)})) 45 | elsif object 46 | D3::Map.new(@d3.JS.map(object)) 47 | else 48 | D3::Map.new(@d3.JS.map()) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/opal/d3/misc.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class << self 3 | def version 4 | `window.d3.version` 5 | end 6 | 7 | def namespaces 8 | `Opal.hash(window.d3.namespaces)` 9 | end 10 | 11 | def namespace(name) 12 | `Opal.hash(window.d3.namespace(name))` 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/opal/d3/native.rb: -------------------------------------------------------------------------------- 1 | require "native" 2 | 3 | module D3 4 | module Native 5 | include ::Native 6 | def initialize(native) 7 | raise unless native 8 | @native = native 9 | end 10 | 11 | def self.included(klass) 12 | klass.extend Helpers 13 | end 14 | 15 | module Helpers 16 | # This provides ruby style and jquery style interfaces: 17 | # obj.foo 18 | # obj.foo = 1; obj.bar = 2 19 | # obj.foo(1).bar(2) 20 | def attribute_d3(ruby_name=nil, js_name) 21 | ruby_name ||= js_name.underscore 22 | define_method(ruby_name) do |new_value=`undefined`| 23 | if `new_value !== undefined` 24 | new_value = `null` if new_value == nil 25 | `self["native"][#{js_name}](#{new_value})` 26 | self 27 | else 28 | value = `self["native"][#{js_name}]()` 29 | `value === null ? nil : value` 30 | end 31 | end 32 | define_method("#{ruby_name}=") do |new_value| 33 | new_value = `null` if new_value == nil 34 | `self["native"][#{js_name}](#{new_value})` 35 | end 36 | end 37 | 38 | # This provides ruby style and jquery style interfaces, 39 | # and also block interface: 40 | # obj.foo 41 | # obj.foo = 1; obj.bar = 2 42 | # obj.foo(1).bar(2).buzz{...} 43 | def attribute_d3_block(ruby_name=nil, js_name) 44 | ruby_name ||= js_name.underscore 45 | define_method(ruby_name) do |new_value=`undefined`, &block| 46 | if block_given? 47 | @native.JS[js_name].JS.apply(@native, `Opal.to_a(block)`) 48 | self 49 | elsif `new_value !== undefined` 50 | new_value = `null` if new_value == nil 51 | `self["native"][#{js_name}](#{new_value})` 52 | self 53 | else 54 | value = `self["native"][#{js_name}]()` 55 | `value === null ? nil : value` 56 | end 57 | end 58 | define_method("#{ruby_name}=") do |new_value| 59 | new_value = `null` if new_value == nil 60 | `self["native"][#{js_name}](#{new_value})` 61 | end 62 | end 63 | 64 | def alias_native_chainable(ruby_name=nil, js_name) 65 | ruby_name ||= js_name.underscore 66 | define_method(ruby_name) do |*args| 67 | @native.JS[js_name].JS.apply(@native, `Opal.to_a(args)`) 68 | self 69 | end 70 | end 71 | 72 | def alias_native_new(ruby_name=nil, js_name) 73 | ruby_name ||= js_name.underscore 74 | define_method(ruby_name) do |*args| 75 | self.class.new( @native.JS[js_name].JS.apply(@native, `Opal.to_a(args)`) ) 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/opal/d3/nest.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class Nest 3 | def initialize 4 | @native = `window.d3`.JS.nest 5 | @depth = 0 6 | @rollup = false 7 | end 8 | 9 | def key(&block) 10 | @native.JS.key(block) 11 | @depth += 1 12 | self 13 | end 14 | 15 | def sort_keys(key=nil,&block) 16 | if block 17 | raise ArgumentError, "Pass block or :ascending/:descending" 18 | else 19 | if key == :ascending 20 | block = `window.d3.ascending` 21 | elsif key == :descending 22 | block = `window.d3.descending` 23 | else 24 | raise ArgumentError, "Pass block or :ascending/:descending" 25 | end 26 | end 27 | @native.JS.sortKeys(block) 28 | self 29 | end 30 | 31 | def sort_values(key=nil,&block) 32 | if block 33 | raise ArgumentError, "Pass block or :ascending/:descending" 34 | else 35 | if key == :ascending 36 | block = `window.d3.ascending` 37 | elsif key == :descending 38 | block = `window.d3.descending` 39 | else 40 | raise ArgumentError, "Pass block or :ascending/:descending" 41 | end 42 | end 43 | @native.JS.sortValues(block) 44 | self 45 | end 46 | 47 | # This is really attrocious, why don't we just return nested hash? 48 | def map(array) 49 | result = @native.JS.map(array) 50 | map_map(result, @depth) 51 | end 52 | 53 | def entries(array) 54 | result = @native.JS.entries(array) 55 | map_entries(result, @depth) 56 | end 57 | 58 | def object(array) 59 | @native.JS.object(array) 60 | end 61 | 62 | def rollup(&block) 63 | @native.JS.rollup(block) 64 | @rollup = true 65 | self 66 | end 67 | 68 | private 69 | 70 | def map_entries(result, depth) 71 | if depth == 0 72 | result 73 | else 74 | if @rollup and depth == 1 75 | result.map{|o| [`o.key`, `o.value`]} 76 | else 77 | result.map{|o| [`o.key`, map_entries(`o.values`, depth-1)]} 78 | end 79 | end 80 | end 81 | 82 | def map_map(result, depth) 83 | if depth == 0 84 | result 85 | else 86 | output = D3::Map.new(`window.d3.map()`) 87 | D3::Map.new(result).each do |vs, k| 88 | output.set(k, map_map(vs, depth-1)) 89 | end 90 | output 91 | end 92 | end 93 | end 94 | 95 | class << self 96 | def nest 97 | D3::Nest.new 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/opal/d3/ordinal_scale.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class OrdinalScale 3 | include D3::Native 4 | 5 | def call(t) 6 | @native.call(t) 7 | end 8 | attribute_d3 :domain 9 | attribute_d3 :range 10 | alias_native_new :copy 11 | 12 | # D3 is trying to reinvent Ruby symbols here 13 | def unknown(new_value=nil) 14 | if new_value == nil 15 | v = @native.JS.unknown 16 | if `JSON.stringify(v) === '{"name":"implicit"}'` 17 | :implicit 18 | else 19 | v 20 | end 21 | else 22 | if new_value == :implicit 23 | new_value = `window.d3.scaleImplicit` 24 | end 25 | @native.JS.unknown(new_value) 26 | self 27 | end 28 | end 29 | end 30 | 31 | class << self 32 | def scale_ordinal(*args) 33 | D3::OrdinalScale.new @d3.JS.scaleOrdinal(*args) 34 | end 35 | 36 | def scale_implicit 37 | :implicit 38 | end 39 | 40 | def scheme_category_10 41 | `window.d3.schemeCategory10` 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/opal/d3/path.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class Path 3 | include D3::Native 4 | 5 | # D3 methods aren't chainable, but there's no reason why they shouldn't be 6 | alias_native_chainable :moveTo 7 | alias_native_chainable :closePath 8 | alias_native_chainable :lineTo 9 | alias_native_chainable :quadraticCurveTo 10 | alias_native_chainable :bezierCurveTo 11 | alias_native_chainable :arcTo 12 | alias_native_chainable :arc 13 | alias_native_chainable :rect 14 | alias_native :to_s, :toString 15 | end 16 | 17 | class << self 18 | def path 19 | D3::Path.new @d3.JS.path 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/opal/d3/pie.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class PieGenerator 3 | include D3::Native 4 | def call(*args) 5 | @native.call(*args).map{|o| 6 | `Opal.hash({ data: o.data, index: o.index, value: o.value, start_angle: o.startAngle, end_angle: o.endAngle, pad_angle: o.padAngle })` 7 | } 8 | end 9 | 10 | attribute_d3_block :startAngle 11 | attribute_d3_block :endAngle 12 | attribute_d3_block :padAngle 13 | attribute_d3_block :value 14 | attribute_d3_block :sort 15 | attribute_d3_block :sortValues 16 | end 17 | 18 | class << self 19 | def pie 20 | D3::PieGenerator.new @d3.JS.pie 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/opal/d3/point_scale.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class PointScale 3 | include D3::Native 4 | 5 | def call(t) 6 | v = @native.call(t) 7 | `v === undefined ? nil : v` 8 | end 9 | attribute_d3 :domain 10 | attribute_d3 :range 11 | alias_native_new :copy 12 | alias_native :bandwidth 13 | alias_native :step 14 | attribute_d3 :padding 15 | attribute_d3 :align 16 | attribute_d3 :round 17 | # This requires argument, we might redo this not to 18 | alias_native_chainable :rangeRound 19 | end 20 | 21 | class << self 22 | def scale_point 23 | D3::PointScale.new @d3.JS.scalePoint 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/opal/d3/polygon.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class << self 3 | # This could maybe use more OO interface in addition to this? 4 | # Like D3::Polygon.new(points).area etc. 5 | 6 | alias_d3 :polygonArea # signed area 7 | alias_d3 :polygonCentroid 8 | alias_d3 :polygon_contains?, :polygonContains 9 | alias_d3 :polygonLength 10 | 11 | def polygon_hull(points) 12 | hull = @d3.JS.polygonHull(points) 13 | `hull === null ? nil : hull` 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/opal/d3/quadtree.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class Quad 3 | include D3::Native 4 | 5 | def internal? 6 | `#@native.constructor === Array` 7 | end 8 | 9 | def leaf? 10 | `#@native.constructor !== Array` 11 | end 12 | 13 | def next 14 | return nil if internal? 15 | return nil if `#@native.next == undefined` 16 | D3::Quad.new(`#@native.next`) 17 | end 18 | 19 | def data 20 | return nil if internal? 21 | `#@native.data` 22 | end 23 | 24 | def children 25 | return nil if leaf? 26 | result = (0..3).map do |i| 27 | q = `#@native[i]` 28 | if `q == null` 29 | nil 30 | else 31 | D3::Quad.new(q) 32 | end 33 | end 34 | end 35 | end 36 | 37 | class QuadTree 38 | include D3::Native 39 | alias_native :data 40 | alias_native :find 41 | alias_native :size 42 | alias_native_chainable :add 43 | alias_native_chainable :addAll 44 | alias_native_chainable :cover 45 | alias_native_chainable :remove 46 | alias_native_chainable :removeAll 47 | alias_native_new :copy 48 | attribute_d3 :extent 49 | 50 | # visit/visitAfter functions have stupid JS habit of using non-nil return as control 51 | # and that messes up with languages which have automatic return 52 | # Maybe worth rewriting to require explicit StopIteration ? 53 | def visit 54 | @native.JS.visit(proc do |node, x0, y0, x1, y1| 55 | yield(D3::Quad.new(node), x0, y0, x1, y1) 56 | end) 57 | self 58 | end 59 | 60 | def visit_after 61 | @native.JS.visitAfter(proc do |node, x0, y0, x1, y1| 62 | yield(D3::Quad.new(node), x0, y0, x1, y1) 63 | end) 64 | self 65 | end 66 | 67 | def root 68 | D3::Quad.new @native.JS.root 69 | end 70 | 71 | def x(&block) 72 | if block_given? 73 | @native.JS.x(block) 74 | self 75 | else 76 | @native.JS.x 77 | end 78 | end 79 | 80 | def y(&block) 81 | if block_given? 82 | @native.JS.y(block) 83 | self 84 | else 85 | @native.JS.y 86 | end 87 | end 88 | end 89 | 90 | class << self 91 | def quadtree(*args) 92 | D3::QuadTree.new @d3.JS.quadtree(*args) 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/opal/d3/quantile_scale.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class QuantileScale 3 | include D3::Native 4 | 5 | def call(t) 6 | @native.call(t) 7 | end 8 | 9 | attribute_d3 :domain 10 | attribute_d3 :range 11 | alias_native :invert_extent, :invertExtent 12 | alias_native :quantiles 13 | alias_native_new :copy 14 | end 15 | 16 | class << self 17 | def scale_quantile 18 | D3::QuantileScale.new @d3.JS.scaleQuantile 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/opal/d3/quantize_scale.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class QuantizeScale 3 | include D3::Native 4 | 5 | def call(t) 6 | @native.call(t) 7 | end 8 | 9 | attribute_d3 :domain 10 | attribute_d3 :range 11 | alias_native :invert_extent, :invertExtent 12 | alias_native :ticks 13 | alias_native :tick_format, :tickFormat 14 | alias_native_chainable :nice 15 | alias_native_new :copy 16 | end 17 | 18 | class << self 19 | def scale_quantize 20 | D3::QuantizeScale.new @d3.JS.scaleQuantize 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/opal/d3/radial_area.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class RadialAreaGenerator 3 | include D3::Native 4 | 5 | def call(*args) 6 | result = @native.call(*args) 7 | `result === null ? nil : result` 8 | end 9 | 10 | attribute_d3_block :angle 11 | attribute_d3_block :startAngle 12 | attribute_d3_block :endAngle 13 | attribute_d3_block :radius 14 | attribute_d3_block :innerRadius 15 | attribute_d3_block :outerRadius 16 | attribute_d3_block :defined 17 | 18 | def curve(new_value=`undefined`) 19 | if `new_value === undefined` 20 | D3::Curve.new @native.JS.curve 21 | else 22 | @native.JS.curve(new_value.to_n) 23 | self 24 | end 25 | end 26 | 27 | def line_start_angle 28 | D3::RadialLineGenerator.new @native.JS.lineStartAngle 29 | end 30 | 31 | def line_end_angle 32 | D3::RadialLineGenerator.new @native.JS.lineEndAngle 33 | end 34 | 35 | def line_inner_radius 36 | D3::RadialLineGenerator.new @native.JS.lineInnerRadius 37 | end 38 | 39 | def line_outer_radius 40 | D3::RadialLineGenerator.new @native.JS.lineOuterRadius 41 | end 42 | end 43 | 44 | class << self 45 | def radial_area 46 | D3::RadialAreaGenerator.new @d3.JS.radialArea 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/opal/d3/radial_line.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class RadialLineGenerator 3 | include D3::Native 4 | 5 | def call(*args) 6 | result = @native.call(*args) 7 | `result === null ? nil : result` 8 | end 9 | 10 | attribute_d3_block :angle 11 | attribute_d3_block :radius 12 | attribute_d3_block :defined 13 | 14 | def curve(new_value=`undefined`) 15 | if `new_value === undefined` 16 | D3::Curve.new @native.JS.curve 17 | else 18 | @native.JS.curve(new_value.to_n) 19 | self 20 | end 21 | end 22 | end 23 | 24 | class << self 25 | def radial_line 26 | D3::RadialLineGenerator.new @d3.JS.radialLine 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/opal/d3/random.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class << self 3 | # These return functions, 4 | # which doesn't feel very Ruby 5 | alias_d3 :randomUniform 6 | alias_d3 :randomNormal 7 | alias_d3 :randomLogNormal 8 | alias_d3 :randomBates 9 | alias_d3 :randomIrwinHall 10 | alias_d3 :randomExponential 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/opal/d3/request.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class << self 3 | def csv(url, &callback) 4 | if callback 5 | @d3.JS.csv(url, callback) 6 | else 7 | @d3.JS.csv(url) 8 | end 9 | end 10 | 11 | def html(url, &callback) 12 | if callback 13 | @d3.JS.html(url, callback) 14 | else 15 | @d3.JS.html(url) 16 | end 17 | end 18 | 19 | def json(url, &callback) 20 | if callback 21 | @d3.JS.json(url, callback) 22 | else 23 | @d3.JS.json(url) 24 | end 25 | end 26 | 27 | def text(url, &callback) 28 | if callback 29 | @d3.JS.text(url, callback) 30 | else 31 | @d3.JS.text(url) 32 | end 33 | end 34 | 35 | def tsv(url, &callback) 36 | if callback 37 | @d3.JS.tsv(url, callback) 38 | else 39 | @d3.JS.tsv(url) 40 | end 41 | end 42 | 43 | def xml(url, &callback) 44 | if callback 45 | @d3.JS.xml(url, callback) 46 | else 47 | @d3.JS.xml(url) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/opal/d3/search.rb: -------------------------------------------------------------------------------- 1 | # all these methods return indexes not values 2 | module D3 3 | class Bisector 4 | include D3::Native 5 | alias_native :left 6 | alias_native :right 7 | end 8 | 9 | class << self 10 | alias_d3 :ascending 11 | alias_d3 :descending 12 | alias_d3 :bisect 13 | alias_d3 :bisectLeft 14 | alias_d3 :bisectRight 15 | 16 | def scan(array, &block) 17 | if block_given? 18 | @d3.JS.scan(array, block) 19 | else 20 | @d3.JS.scan(array) 21 | end 22 | end 23 | 24 | def bisector(&block) 25 | D3::Bisector.new @d3.JS.bisector(block) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/opal/d3/selection.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class Selection 3 | include D3::Native 4 | alias_native :size 5 | alias_native :empty?, :empty 6 | alias_native :node 7 | alias_native :nodes 8 | 9 | def inspect 10 | `#@native.toString()` 11 | end 12 | 13 | attribute_d3_block :text 14 | attribute_d3_block :html 15 | 16 | def data(data=nil,key=nil,&block) 17 | if block 18 | self.class.new @native.JS.data(block) 19 | elsif key != nil 20 | self.class.new @native.JS.data(data,key) 21 | elsif data != nil 22 | self.class.new @native.JS.data(data) 23 | else 24 | @native.JS.data 25 | end 26 | end 27 | 28 | def append(name=`undefined`, &block) 29 | raise if `name !== undefined` and block_given? 30 | name = block if block_given? 31 | D3::Selection.new @native.JS.append(name.to_n) 32 | end 33 | 34 | def insert(name=`undefined`, before=`undefined`, &block) 35 | if `name === undefined` 36 | raise unless block_given? 37 | D3::Selection.new @native.JS.insert(block.to_n) 38 | elsif `before === undefined` 39 | if block_given? 40 | D3::Selection.new @native.JS.insert(name.to_n, block.to_n) 41 | else 42 | D3::Selection.new @native.JS.insert(name.to_n) 43 | end 44 | else 45 | raise if block_given? 46 | D3::Selection.new @native.JS.insert(name.to_n, before.to_n) 47 | end 48 | end 49 | 50 | alias_native_new :remove 51 | alias_native_new :select 52 | alias_native_new :selectAll 53 | alias_native_new :enter 54 | alias_native_new :exit 55 | alias_native :call 56 | alias_native_chainable :raise 57 | alias_native_chainable :lower 58 | 59 | def merge(other) 60 | D3::Selection.new @native.JS.merge(other.to_n) 61 | end 62 | 63 | def classed(classes, yesno=`undefined`, &block) 64 | if block_given? 65 | @native.JS.classed(classes, block) 66 | self 67 | elsif `yesno !== undefined` 68 | @native.JS.classed(classes, yesno) 69 | self 70 | else 71 | @native.JS.classed(classes) 72 | end 73 | end 74 | 75 | def filter(other=`undefined`,&block) 76 | if block_given? 77 | D3::Selection.new @native.JS.filter(block) 78 | else 79 | D3::Selection.new @native.JS.filter(other) 80 | end 81 | end 82 | 83 | def each(other=`undefined`,&block) 84 | if block_given? 85 | @native.JS.each(block) 86 | else 87 | @native.JS.each(other) 88 | end 89 | self 90 | end 91 | 92 | def property(key, value=`undefined`) 93 | if `value === undefined` 94 | @native.JS.property(key) 95 | else 96 | @native.JS.property(key, value) 97 | self 98 | end 99 | end 100 | 101 | # Usage: 102 | # style("foo") 103 | # style("foo"){ value } 104 | # style("foo", value) 105 | # style("foo", value, priority) 106 | def style(name, value=`undefined`, priority=`undefined`, &block) 107 | if block 108 | raise if `name === undefined` or `priority !== undefined` or `value !== undefined` 109 | Selection.new @native.JS.style(name, block) 110 | elsif `value === undefined` 111 | @native.JS.style(name) 112 | else 113 | value = `value === nil ? null : value` 114 | Selection.new @native.JS.style(name, value, priority) 115 | end 116 | end 117 | 118 | # nil means something specific (reset value), 119 | # so we need another special value 120 | def attr(name, value=`undefined`, &block) 121 | if block 122 | @native.JS.attr(name, block) 123 | self 124 | elsif `value == undefined` 125 | @native.JS.attr(name) 126 | elsif value == nil 127 | @native.JS.attr(name, `null`) 128 | self 129 | else 130 | @native.JS.attr(name, value) 131 | self 132 | end 133 | end 134 | 135 | # This is not tested, and events are not wrapped in any nice way 136 | def on(name, &callback) 137 | if callback 138 | @native.JS.on(name, callback) 139 | self 140 | else 141 | @native.JS.on(name) 142 | end 143 | end 144 | end 145 | 146 | class << self 147 | def selection 148 | D3::Selection.new(@d3.JS.selection) 149 | end 150 | 151 | def select(selector) 152 | D3::Selection.new(@d3.JS.select(selector)) 153 | end 154 | 155 | def select_all(selector) 156 | D3::Selection.new(@d3.JS.selectAll(selector)) 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /lib/opal/d3/sequential_scale.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class SequentialScale 3 | include D3::Native 4 | 5 | def call(t) 6 | @native.call(t) 7 | end 8 | 9 | attribute_d3 :domain 10 | attribute_d3 :clamp 11 | 12 | def copy 13 | self.class.new @native.JS.copy 14 | end 15 | 16 | def interpolator(&block) 17 | if block_given? 18 | @native.JS.interpolator(block) 19 | self 20 | else 21 | @native.JS.interpolator 22 | end 23 | end 24 | end 25 | 26 | class << self 27 | def scale_sequential(&block) 28 | raise unless block_given? 29 | SequentialScale.new @d3.JS.scaleSequential(block) 30 | end 31 | 32 | def interpolate_viridis(t=nil) 33 | if t 34 | @d3.JS.interpolateViridis(t) 35 | else 36 | `#@d3.interpolateViridis` 37 | end 38 | end 39 | 40 | def interpolate_inferno(t=nil) 41 | if t 42 | @d3.JS.interpolateInferno(t) 43 | else 44 | `#@d3.interpolateInferno` 45 | end 46 | end 47 | 48 | def interpolate_magma(t=nil) 49 | if t 50 | @d3.JS.interpolateMagma(t) 51 | else 52 | `#@d3.interpolateMagma` 53 | end 54 | end 55 | 56 | def interpolate_plasma(t=nil) 57 | if t 58 | @d3.JS.interpolatePlasma(t) 59 | else 60 | `#@d3.interpolatePlasma` 61 | end 62 | end 63 | 64 | def interpolate_warm(t=nil) 65 | if t 66 | @d3.JS.interpolateWarm(t) 67 | else 68 | `#@d3.interpolateWarm` 69 | end 70 | end 71 | 72 | def interpolate_cool(t=nil) 73 | if t 74 | @d3.JS.interpolateCool(t) 75 | else 76 | `#@d3.interpolateCool` 77 | end 78 | end 79 | 80 | def interpolate_rainbow(t=nil) 81 | if t 82 | @d3.JS.interpolateRainbow(t) 83 | else 84 | `#@d3.interpolateRainbow` 85 | end 86 | end 87 | 88 | def interpolate_cubehelix_default(t=nil) 89 | if t 90 | @d3.JS.interpolateCubehelixDefault(t) 91 | else 92 | `#@d3.interpolateCubehelixDefault` 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/opal/d3/set.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class Set 3 | include D3::Native 4 | alias_native :empty?, :empty 5 | alias_native :has?, :has 6 | alias_native :size 7 | alias_native :values 8 | alias_native_chainable :add 9 | alias_native_chainable :clear 10 | alias_native_chainable :remove 11 | 12 | def each(&block) 13 | @native.JS.each(block) 14 | self 15 | end 16 | 17 | def inspect 18 | "#" 19 | end 20 | end 21 | 22 | class << self 23 | def set(array=nil, &block) 24 | if block_given? 25 | D3::Set.new @d3.JS.set(array, proc{|x| yield(x)}) 26 | elsif array 27 | D3::Set.new @d3.JS.set(array) 28 | else 29 | D3::Set.new @d3.JS.set() 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/opal/d3/stack.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class StackGenerator 3 | include D3::Native 4 | end 5 | 6 | class << self 7 | def stack 8 | D3::StackGenerator.new @d3.JS.stack 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/opal/d3/statistics.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class << self 3 | def min(data, &block) 4 | if block 5 | @d3.JS.min(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 6 | else 7 | @d3.JS.min(data.compact) 8 | end 9 | end 10 | 11 | def max(data, &block) 12 | if block 13 | @d3.JS.max(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 14 | else 15 | @d3.JS.max(data.compact) 16 | end 17 | end 18 | 19 | def sum(data, &block) 20 | if block 21 | @d3.JS.sum(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 22 | else 23 | @d3.JS.sum(data.compact) 24 | end 25 | end 26 | 27 | def mean(data, &block) 28 | result = if block 29 | @d3.JS.mean(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 30 | else 31 | @d3.JS.mean(data.compact) 32 | end 33 | `result === undefined ? nil : result` 34 | end 35 | 36 | def median(data, &block) 37 | result = if block 38 | @d3.JS.median(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 39 | else 40 | @d3.JS.median(data.compact) 41 | end 42 | `result === undefined ? nil : result` 43 | end 44 | 45 | def deviation(data, &block) 46 | result = if block 47 | @d3.JS.deviation(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 48 | else 49 | @d3.JS.deviation(data.compact) 50 | end 51 | `result === undefined ? nil : result` 52 | end 53 | 54 | def variance(data, &block) 55 | result = if block 56 | @d3.JS.variance(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 57 | else 58 | @d3.JS.variance(data.compact) 59 | end 60 | `result === undefined ? nil : result` 61 | end 62 | 63 | def quantile(data, p, &block) 64 | result = if block 65 | @d3.JS.quantile(data, p, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 66 | else 67 | @d3.JS.quantile(data.compact, p) 68 | end 69 | `result === undefined ? nil : result` 70 | end 71 | 72 | def extent(data, &block) 73 | (a,b) = if block 74 | @d3.JS.extent(data, proc{|x| y=yield(x); y.nil? ? `undefined` : y}) 75 | else 76 | @d3.JS.extent(data.compact) 77 | end 78 | [`a === undefined ? nil : a`, `b === undefined ? nil : b`] 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/opal/d3/symbol.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class SymbolGenerator 3 | include D3::Native 4 | 5 | def call(*args) 6 | @native.call(*args) 7 | end 8 | 9 | attribute_d3_block :size 10 | 11 | def type(new_value=`undefined`, &block) 12 | if block_given? 13 | @native.JS.type{|*args| yield(*args).to_n} 14 | self 15 | elsif `new_value === undefined` 16 | D3::SymbolType.new @native.JS.type 17 | else 18 | @native.JS.type(new_value.to_n) 19 | self 20 | end 21 | end 22 | 23 | def type=(new_value) 24 | @native.type(new_value.to_n) 25 | self 26 | end 27 | end 28 | 29 | class SymbolType 30 | include D3::Native 31 | end 32 | 33 | class << self 34 | def symbol 35 | D3::SymbolGenerator.new @d3.JS.symbol 36 | end 37 | 38 | def symbols 39 | `window.d3.symbols`.map{|st| D3::SymbolType.new(st)} 40 | end 41 | 42 | def symbol_circle 43 | D3::SymbolType.new `window.d3.symbolCircle` 44 | end 45 | 46 | def symbol_cross 47 | D3::SymbolType.new `window.d3.symbolCross` 48 | end 49 | 50 | def symbol_diamond 51 | D3::SymbolType.new `window.d3.symbolDiamond` 52 | end 53 | 54 | def symbol_square 55 | D3::SymbolType.new `window.d3.symbolSquare` 56 | end 57 | 58 | def symbol_star 59 | D3::SymbolType.new `window.d3.symbolStar` 60 | end 61 | 62 | def symbol_triangle 63 | D3::SymbolType.new `window.d3.symbolTriangle` 64 | end 65 | 66 | def symbol_wye 67 | D3::SymbolType.new `window.d3.symbolWye` 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/opal/d3/threshold_scale.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class ThresholdScale 3 | include D3::Native 4 | attribute_d3 :domain 5 | attribute_d3 :range 6 | alias_native_new :copy 7 | 8 | def call(t) 9 | @native.call(t) 10 | end 11 | 12 | def invert_extent(t) 13 | a,b = @native.JS.invertExtent(t) 14 | [`a === undefined ? nil : a`, `b === undefined ? nil : b`] 15 | end 16 | end 17 | 18 | class << self 19 | def scale_threshold 20 | D3::ThresholdScale.new @d3.JS.scaleThreshold 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/opal/d3/time_format.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class TimeFormatLocale 3 | include D3::Native 4 | alias_native :format, :format 5 | alias_native :parse, :parse 6 | alias_native :utc_format, :utcFormat 7 | alias_native :utc_parse, :utcParse 8 | end 9 | 10 | class << self 11 | alias_d3 :timeFormat 12 | alias_d3 :timeParse 13 | alias_d3 :utcFormat 14 | alias_d3 :utcParse 15 | alias_d3 :isoFormat 16 | alias_d3 :isoParse 17 | 18 | def time_format_locale(spec={}) 19 | D3::TimeFormatLocale.new @d3.JS.timeFormatLocale( 20 | { 21 | dateTime: spec.fetch(:date_time, "%x, %X"), 22 | date: spec.fetch(:date, "%-m/%-d/%Y"), 23 | time: spec.fetch(:time, "%-I:%M:%S %p"), 24 | periods: spec.fetch(:periods, ["AM", "PM"]), 25 | days: spec.fetch(:days, ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]), 26 | shortDays: spec.fetch(:short_days, ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]), 27 | months: spec.fetch(:months, ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]), 28 | shortMonths: spec.fetch(:short_months, ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]), 29 | }.to_n 30 | ) 31 | end 32 | 33 | def time_format_default_locale(spec={}) 34 | D3::TimeFormatLocale.new @d3.JS.timeFormatDefaultLocale( 35 | { 36 | dateTime: spec.fetch(:date_time, "%x, %X"), 37 | date: spec.fetch(:date, "%-m/%-d/%Y"), 38 | time: spec.fetch(:time, "%-I:%M:%S %p"), 39 | periods: spec.fetch(:periods, ["AM", "PM"]), 40 | days: spec.fetch(:days, ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]), 41 | shortDays: spec.fetch(:short_days, ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]), 42 | months: spec.fetch(:months, ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]), 43 | shortMonths: spec.fetch(:short_months, ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]), 44 | }.to_n 45 | ) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/opal/d3/time_interval.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class TimeInterval 3 | include D3::Native 4 | 5 | alias_native :round 6 | alias_native :floor 7 | alias_native :ceil 8 | alias_native :offset 9 | alias_native :range 10 | alias_native :count 11 | alias_native_new :every 12 | 13 | def filter(&block) 14 | D3::TimeInterval.new(@native.JS.filter(block)) 15 | end 16 | end 17 | 18 | class << self 19 | def time_year; D3::TimeInterval.new(`window.d3.timeYear`); end 20 | def utc_year; D3::TimeInterval.new(`window.d3.utcYear`); end 21 | def time_month; D3::TimeInterval.new(`window.d3.timeMonth`); end 22 | def utc_month; D3::TimeInterval.new(`window.d3.utcMonth`); end 23 | def time_week; D3::TimeInterval.new(`window.d3.timeWeek`); end 24 | def utc_week; D3::TimeInterval.new(`window.d3.utcWeek`); end 25 | def time_monday; D3::TimeInterval.new(`window.d3.timeMonday`); end 26 | def utc_monday; D3::TimeInterval.new(`window.d3.utcMonday`); end 27 | def time_tuesday; D3::TimeInterval.new(`window.d3.timeTuesday`); end 28 | def utc_tuesday; D3::TimeInterval.new(`window.d3.utcTuesday`); end 29 | def time_wednesday; D3::TimeInterval.new(`window.d3.timeWednesday`); end 30 | def utc_wednesday; D3::TimeInterval.new(`window.d3.utcWednesday`); end 31 | def time_thursday; D3::TimeInterval.new(`window.d3.timeThursday`); end 32 | def utc_thursday; D3::TimeInterval.new(`window.d3.utcThursday`); end 33 | def time_friday; D3::TimeInterval.new(`window.d3.timeFriday`); end 34 | def utc_friday; D3::TimeInterval.new(`window.d3.utcFriday`); end 35 | def time_saturday; D3::TimeInterval.new(`window.d3.timeSaturday`); end 36 | def utc_saturday; D3::TimeInterval.new(`window.d3.utcSaturday`); end 37 | def time_sunday; D3::TimeInterval.new(`window.d3.timeSunday`); end 38 | def utc_sunday; D3::TimeInterval.new(`window.d3.utcSunday`); end 39 | def time_day; D3::TimeInterval.new(`window.d3.timeDay`); end 40 | def utc_day; D3::TimeInterval.new(`window.d3.utcDay`); end 41 | def time_hour; D3::TimeInterval.new(`window.d3.timeHour`); end 42 | def utc_hour; D3::TimeInterval.new(`window.d3.utcHour`); end 43 | def time_minute; D3::TimeInterval.new(`window.d3.timeMinute`); end 44 | def utc_minute; D3::TimeInterval.new(`window.d3.utcMinute`); end 45 | def time_second; D3::TimeInterval.new(`window.d3.timeSecond`); end 46 | def utc_second; D3::TimeInterval.new(`window.d3.utcSecond`); end 47 | def time_millisecond; D3::TimeInterval.new(`window.d3.timeMillisecond`); end 48 | def utc_millisecond; D3::TimeInterval.new(`window.d3.utcMillisecond`); end 49 | 50 | alias_d3 :timeYears 51 | alias_d3 :utcYears 52 | alias_d3 :timeMilliseconds 53 | alias_d3 :utcMilliseconds 54 | alias_d3 :timeSeconds 55 | alias_d3 :utcSeconds 56 | alias_d3 :timeMinutes 57 | alias_d3 :utcMinutes 58 | alias_d3 :timeHours 59 | alias_d3 :utcHours 60 | alias_d3 :timeDays 61 | alias_d3 :utcDays 62 | alias_d3 :timeWeeks 63 | alias_d3 :utcWeeks 64 | alias_d3 :timeSundays 65 | alias_d3 :utcSundays 66 | alias_d3 :timeMondays 67 | alias_d3 :utcMondays 68 | alias_d3 :timeTuesdays 69 | alias_d3 :utcTuesdays 70 | alias_d3 :timeWednesdays 71 | alias_d3 :utcWednesdays 72 | alias_d3 :timeThursdays 73 | alias_d3 :utcThursdays 74 | alias_d3 :timeFridays 75 | alias_d3 :utcFridays 76 | alias_d3 :timeSaturdays 77 | alias_d3 :utcSaturdays 78 | alias_d3 :timeMonths 79 | alias_d3 :utcMonths 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/opal/d3/transformations.rb: -------------------------------------------------------------------------------- 1 | module D3 2 | class << self 3 | alias_d3 :merge 4 | alias_d3 :pairs 5 | alias_d3 :permute 6 | alias_d3 :shuffle 7 | alias_d3 :ticks 8 | alias_d3 :tickStep 9 | alias_d3 :range 10 | alias_d3 :transpose 11 | alias_d3 :zip 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/opal/d3/version.rb: -------------------------------------------------------------------------------- 1 | module Opal 2 | module D3 3 | VERSION = "0.0.20170822" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /opal-d3.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/opal/d3/version" 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "opal-d3" 5 | s.version = Opal::D3::VERSION 6 | s.author = "Tomasz Wegrzanowski" 7 | s.email = "Tomasz.Wegrzanowski@gmail.com" 8 | s.homepage = "https://github.com/taw/opal-d3" 9 | s.summary = "Ruby bindings for D3" 10 | s.description = "Opal wrapper library for D3 library" 11 | 12 | s.files = `git ls-files`.split("\n") 13 | s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } 14 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 15 | s.require_paths = ["lib"] 16 | 17 | s.add_runtime_dependency "opal", "~> 0.10.0" 18 | s.add_runtime_dependency "opal-activesupport", "= 0.3.1" 19 | s.add_development_dependency "opal-rspec", ">= 0.6.0" 20 | s.add_development_dependency "rake", ">= 11.3.0" 21 | end 22 | -------------------------------------------------------------------------------- /spec/arc_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - arc" do 2 | it "d3.arc" do 3 | expect(D3.arc).to be_instance_of(D3::ArcGenerator) 4 | end 5 | 6 | let(:example_arc) { 7 | D3.arc 8 | .inner_radius(0) 9 | .outer_radius(100) 10 | .start_angle(0) 11 | .end_angle(Math::PI / 2) 12 | } 13 | 14 | # That's a weird rounding error, documentation says "M0,-100A100,100,0,0,1,100,0L0,0Z" 15 | it "basics" do 16 | expect(example_arc.()).to eq("M6.123233995736766e-15,-100A100,100,0,0,1,100,0L0,0Z") 17 | end 18 | 19 | it "passing hash" do 20 | arc = D3.arc 21 | expect(arc.({ 22 | innerRadius: 0, 23 | outerRadius: 100, 24 | startAngle: 0, 25 | endAngle: Math::PI / 2, 26 | }.to_n)).to eq("M6.123233995736766e-15,-100A100,100,0,0,1,100,0L0,0Z") 27 | end 28 | 29 | it "arc.inner_radius" do 30 | arc = D3.arc.inner_radius(20) 31 | expect(arc.inner_radius.()).to eq(20) 32 | arc.inner_radius{|t| t*10} 33 | expect(arc.inner_radius.(12)).to eq(120) 34 | end 35 | 36 | it "arc.outer_radius" do 37 | arc = D3.arc.outer_radius(100) 38 | expect(arc.outer_radius.()).to eq(100) 39 | arc.outer_radius{|t| t*10} 40 | expect(arc.outer_radius.(12)).to eq(120) 41 | end 42 | 43 | it "arc.start_angle" do 44 | arc = D3.arc.start_angle(Math::PI/2) 45 | expect(arc.start_angle.()).to eq(Math::PI/2) 46 | arc.start_angle{|t| t*10} 47 | expect(arc.start_angle.(12)).to eq(120) 48 | end 49 | 50 | it "arc.end_angle" do 51 | arc = D3.arc.end_angle(3*Math::PI/2) 52 | expect(arc.end_angle.()).to eq(3*Math::PI/2) 53 | arc.end_angle{|t| t*10} 54 | expect(arc.end_angle.(12)).to eq(120) 55 | end 56 | 57 | it "arc.corner_radius" do 58 | arc = D3.arc.corner_radius(5) 59 | expect(arc.corner_radius.()).to eq(5) 60 | arc.corner_radius{|t| t*10} 61 | expect(arc.corner_radius.(12)).to eq(120) 62 | expect(example_arc.corner_radius(5).()).to eq( 63 | "M6.217248937900877e-15,-94.86832980505139"+ 64 | "A5,5,0,0,1,5.263157894736849,-99.86139979479094"+ 65 | "A100,100,0,0,1,99.86139979479093,-5.263157894736842"+ 66 | "A5,5,0,0,1,94.86832980505139,0"+ 67 | "L0,0Z" 68 | ) 69 | end 70 | 71 | it "arc.centroid" do 72 | expect(example_arc.centroid).to eq([35.35533905932738, -35.35533905932737]) 73 | expect(D3.arc.centroid({ 74 | innerRadius: 20, 75 | outerRadius: 40, 76 | startAngle: Math::PI * 0.25, 77 | endAngle: Math::PI * 0.75 , 78 | }.to_n)).to eq([30,0]) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/area_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - area" do 2 | it "d3.area" do 3 | expect(D3.area).to be_instance_of(D3::AreaGenerator) 4 | end 5 | 6 | let(:simple_data) {[ 7 | {year: 2007, value: 93.24}, 8 | {year: 2008, value: 95.35}, 9 | {year: 2009, value: 98.84}, 10 | {year: 2010, value: 99.92}, 11 | {year: 2011, value: 99.80}, 12 | {year: 2014, value: 99.47}, 13 | ]} 14 | 15 | describe "basics" do 16 | let(:area) { 17 | D3.area 18 | .x{|d| (d[:year]-2000)*10 } 19 | .y1{|d| d[:value] } 20 | .y0(50) 21 | } 22 | it "basics" do 23 | expect(area.(simple_data)).to eq( 24 | "M70,93.24L80,95.35L90,98.84L100,99.92L110,99.8L140,99.47L140,50L110,50L100,50L90,50L80,50L70,50Z" 25 | ) 26 | end 27 | 28 | it "curve" do 29 | expect(area.curve).to be_instance_of(D3::Curve) 30 | expect(area.curve(D3.curve_natural).(simple_data)).to eq(%W[ 31 | M70,93.24 32 | C73.3652312599681,93.76974481658695,76.7304625199362,94.29948963317388,80,95.35 33 | C83.2695374800638,96.40051036682611,86.4433811802233,97.97178628389153,90,98.84 34 | C93.5566188197767,99.70821371610847,97.49601275917065,99.87336523125998,100,99.92 35 | C102.50398724082935,99.96663476874002,103.5725677830941,99.89475279106857,110,99.8 36 | C116.4274322169059,99.70524720893143,128.21371610845296,99.5876236044657,140,99.47L140,50 37 | C128.21371610845296,50,116.4274322169059,50,110,50 38 | C103.5725677830941,50,102.50398724082936,50,100,50 39 | C97.49601275917064,50,93.55661881977672,50,90,50 40 | C86.44338118022328,50,83.2695374800638,50,80,50 41 | C76.7304625199362,50,73.3652312599681,50,70,50Z 42 | ].join) 43 | end 44 | 45 | it ".defined" do 46 | expect(area.defined{|d| d[:year] != 2010}.(simple_data)).to eq( 47 | "M70,93.24L80,95.35L90,98.84L90,50L80,50L70,50ZM110,99.8L140,99.47L140,50L110,50Z" 48 | ) 49 | end 50 | end 51 | 52 | describe "line generators" do 53 | let(:area) { 54 | D3.area 55 | .x0{|d| (d[:year]-2000)*10 } 56 | .x1{|d| (d[:year]-2000)*20 } 57 | .y1{|d| d[:value] } 58 | .y0(50) 59 | } 60 | let(:point) { {year: 2042, value: 117 } } 61 | 62 | it ".x/.x0/.x1" do 63 | expect(area.x0.(point)).to eq(420) 64 | expect(area.x1.(point)).to eq(840) 65 | expect(area.x.(point)).to eq(420) 66 | end 67 | 68 | it ".y/.y0/.y1" do 69 | expect(area.y0.(point)).to eq(50) 70 | expect(area.y1.(point)).to eq(117) 71 | expect(area.y.(point)).to eq(50) 72 | end 73 | 74 | it ".line_x0" do # x0 y0 75 | expect(area.line_x0).to be_instance_of(D3::LineGenerator) 76 | expect(area.line_x0.x.(point)).to eq(420) 77 | expect(area.line_x0.y.(point)).to eq(50) 78 | end 79 | 80 | it ".line_x1" do # x1 y0 81 | expect(area.line_x1).to be_instance_of(D3::LineGenerator) 82 | expect(area.line_x1.x.(point)).to eq(840) 83 | expect(area.line_x1.y.(point)).to eq(50) 84 | end 85 | 86 | it ".line_y0" do # x0 y0 87 | expect(area.line_y0).to be_instance_of(D3::LineGenerator) 88 | expect(area.line_y0.x.(point)).to eq(420) 89 | expect(area.line_y0.y.(point)).to eq(50) 90 | end 91 | 92 | it ".line_y1" do # x0 y1 93 | expect(area.line_y1).to be_instance_of(D3::LineGenerator) 94 | expect(area.line_y1.x.(point)).to eq(420) 95 | expect(area.line_y1.y.(point)).to eq(117) 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/band_scale_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - band scale" do 2 | it "d3.scale_band" do 3 | expect(D3.scale_band).to be_instance_of(D3::BandScale) 4 | end 5 | 6 | let(:d) { ("a".."f").to_a } 7 | it "basics" do 8 | scale = D3.scale_band.domain(d) 9 | expect(d.map{|v| scale.(v).round(2)}).to eq([0, 0.17, 0.33, 0.5, 0.67, 0.83]) 10 | expect(scale.bandwidth).to eq(0.16666666666666666) 11 | expect(scale.step).to eq(0.16666666666666666) 12 | expect(scale.("z")).to eq(nil) 13 | end 14 | 15 | it "padding" do 16 | scale = D3.scale_band.domain(d).range([0,100]) 17 | expect(scale.padding).to eq(0) 18 | expect(scale.padding_inner).to eq(0) 19 | expect(scale.padding_outer).to eq(0) 20 | 21 | scale.padding(0.5) 22 | expect(scale.padding).to eq(0.5) 23 | expect(scale.padding_inner).to eq(0.5) 24 | expect(scale.padding_outer).to eq(0.5) 25 | 26 | scale.padding_inner(0.2) 27 | scale.padding_outer(0.4) 28 | expect(scale.padding).to eq(0.2) 29 | expect(scale.padding_inner).to eq(0.2) 30 | expect(scale.padding_outer).to eq(0.4) 31 | 32 | expect(d.map{|v| scale.(v).round(2)}).to eq([6.06, 21.21, 36.36, 51.52, 66.67, 81.82]) 33 | expect(scale.bandwidth).to eq(12.121212121212123) 34 | expect(scale.step).to eq(15.151515151515152) 35 | end 36 | 37 | it "copy" do 38 | scale = D3.scale_band.domain(d) 39 | sc = scale.copy.range([10,20]) 40 | expect(scale.domain).to eq(d) 41 | expect(sc.domain).to eq(d) 42 | expect(scale.range).to eq([0,1]) 43 | expect(sc.range).to eq([10,20]) 44 | end 45 | 46 | it "align" do 47 | scale = D3.scale_band.domain(d).range([0,100]) 48 | expect(scale.align).to eq(0.5) 49 | scale.padding_inner(0.2) 50 | scale.padding_outer(0.4) 51 | expect(d.map{|v| scale.(v).round(2)}).to eq([6.06, 21.21, 36.36, 51.52, 66.67, 81.82]) 52 | 53 | scale.align(0) 54 | expect(scale.align).to eq(0) 55 | expect(d.map{|v| scale.(v).round(2)}).to eq([0, 15.15, 30.3, 45.45, 60.61, 75.76]) 56 | 57 | scale.align(1) 58 | expect(scale.align).to eq(1) 59 | expect(d.map{|v| scale.(v).round(2)}).to eq([12.12, 27.27, 42.42, 57.58, 72.73, 87.88]) 60 | expect(scale.bandwidth).to eq(12.121212121212123) 61 | expect(scale.step).to eq(15.151515151515152) 62 | end 63 | 64 | it "round" do 65 | scale = D3.scale_band.domain(d).range([0,100]) 66 | expect(scale.round).to eq(false) 67 | scale.range_round([0,1000]) 68 | expect(scale.range).to eq([0,1000]) 69 | expect(scale.round).to eq(true) 70 | # This rounding is somewhat weird 71 | expect(d.map{|v| scale.(v)}).to eq([2, 168, 334, 500, 666, 832]) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/color_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-colors" do 2 | it "d3.color" do 3 | expect(D3.color("blue")).to be_instance_of(D3::Color) 4 | end 5 | 6 | it "supported syntax" do 7 | expect(D3.color("rgb(255, 255, 255)").to_s).to eq("rgb(255, 255, 255)") 8 | expect(D3.color("rgb(10%, 20%, 30%)").to_s).to eq("rgb(26, 51, 77)") 9 | expect(D3.color("rgba(255, 255, 255, 0.4)").to_s).to eq("rgba(255, 255, 255, 0.4)") 10 | expect(D3.color("rgba(10%, 20%, 30%, 0.4)").to_s).to eq("rgba(26, 51, 77, 0.4)") 11 | expect(D3.color("hsl(120, 50%, 20%)").to_s).to eq("rgb(25, 77, 25)") 12 | expect(D3.color("hsla(120, 50%, 20%, 0.4)").to_s).to eq("rgba(25, 77, 25, 0.4)") 13 | expect(D3.color("#ffeeaa").to_s).to eq("rgb(255, 238, 170)") 14 | expect(D3.color("#fea").to_s).to eq("rgb(255, 238, 170)") 15 | expect(D3.color("steelblue").to_s).to eq("rgb(70, 130, 180)") 16 | end 17 | 18 | it "invalid colors" do 19 | expect{ D3.color("poop_emoji") }.to raise_error(/Invalid color/) 20 | end 21 | 22 | it "color.brighter" do 23 | steelblue = D3.color("steelblue") 24 | expect(steelblue.brighter.to_s).to eq("rgb(100, 186, 255)") 25 | expect(steelblue.brighter(0.25).to_s).to eq("rgb(77, 142, 197)") 26 | end 27 | 28 | it "color.darker" do 29 | steelblue = D3.color("steelblue") 30 | expect(steelblue.darker.to_s).to eq("rgb(49, 91, 126)") 31 | expect(steelblue.darker(0.25).to_s).to eq("rgb(64, 119, 165)") 32 | end 33 | 34 | it "color.displayable?" do 35 | expect(D3.color("rgb(255,255,255)")).to be_displayable 36 | expect(D3.color("rgb(260,260,260)")).to_not be_displayable 37 | end 38 | 39 | it "d3.rgb" do 40 | steelblue = D3.rgb("steelblue") 41 | expect([steelblue.r, steelblue.g, steelblue.b]).to eq([70, 130, 180]) 42 | expect(D3.rgb(10,20,30).to_s).to eq("rgb(10, 20, 30)") 43 | expect(D3.rgb(10,20,30,0.5).to_s).to eq("rgba(10, 20, 30, 0.5)") 44 | expect(D3.rgb(D3.hsl("steelblue")).to_s).to eq("rgb(70, 130, 180)") 45 | end 46 | 47 | it "d3.hsl" do 48 | steelblue = D3.hsl("steelblue") 49 | expect([steelblue.h, steelblue.s, steelblue.l]).to eq([207.27272727272728, 0.44, 0.4901960784313726]) 50 | expect(D3.hsl(207.27272727272728, 0.44, 0.4901960784313726).to_s).to eq("rgb(70, 130, 180)") 51 | expect(D3.hsl(207.27272727272728, 0.44, 0.4901960784313726, 0.5).to_s).to eq("rgba(70, 130, 180, 0.5)") 52 | end 53 | 54 | it "d3.lab" do 55 | steelblue = D3.lab("steelblue") 56 | expect([steelblue.l, steelblue.a, steelblue.b]).to eq([52.46551718768575, -4.0774710123572255, -32.19186122981343]) 57 | expect(D3.lab(52.46551718768575, -4.0774710123572255, -32.19186122981343).to_s).to eq("rgb(70, 130, 180)") 58 | expect(D3.lab(52.46551718768575, -4.0774710123572255, -32.19186122981343, 0.5).to_s).to eq("rgba(70, 130, 180, 0.5)") 59 | end 60 | 61 | it "d3.hcl" do 62 | steelblue = D3.hcl("steelblue") 63 | expect([steelblue.h, steelblue.c, steelblue.l]).to eq([262.78126775909277, 32.44906314974561, 52.46551718768575]) 64 | expect(D3.hcl(262.78126775909277, 32.44906314974561, 52.46551718768575).to_s).to eq("rgb(70, 130, 180)") 65 | expect(D3.hcl(262.78126775909277, 32.44906314974561, 52.46551718768575, 0.5).to_s).to eq("rgba(70, 130, 180, 0.5)") 66 | end 67 | 68 | it "d3.cubehelix" do 69 | steelblue = D3.cubehelix("steelblue") 70 | expect([steelblue.h, steelblue.s, steelblue.l]).to eq([202.84837896488932, 0.6273147230777709, 0.460784355920337]) 71 | expect(D3.cubehelix(202.84837896488932, 0.6273147230777709, 0.460784355920337).to_s).to eq("rgb(70, 130, 180)") 72 | expect(D3.cubehelix(202.84837896488932, 0.6273147230777709, 0.460784355920337, 0.5).to_s).to eq("rgba(70, 130, 180, 0.5)") 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/coverage_spec.rb: -------------------------------------------------------------------------------- 1 | describe "D3 coverage" do 2 | # Test code is here because opal-repl doesn't support aggregate_failures mode 3 | native_methods = `Opal.hash(window.d3)`.keys.sort 4 | nice_methods = native_methods.map{|m| m.gsub(/([a-z])(?=[A-Z0-9])/, "\\1_").downcase } 5 | 6 | # format conflict with base ruby 7 | nice_methods.each do |method| 8 | it method do 9 | if D3.respond_to?(method) 10 | # OK 11 | else 12 | skip "not implemented yet" 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/creator_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - creator" do 2 | after(:each) do 3 | D3.select("#test-area").html("") 4 | end 5 | let(:root) { D3.select("#test-area") } 6 | let(:html) { root.html } 7 | 8 | it "d3.creator" do 9 | expect(D3.creator("div")).to be_instance_of(D3::Creator) 10 | D3.select("div").append(D3.creator("span")) 11 | expect(html).to eq( 12 | "" 13 | ) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/format_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - format" do 2 | it "d3.format" do 3 | expect(D3.format(".2").(42)).to eq("42") 4 | expect(D3.format(".2").(4.2)).to eq("4.2") 5 | expect(D3.format(".1").(42)).to eq("4e+1") 6 | expect(D3.format(".1").(4.2)).to eq("4") 7 | expect(D3.format(".0%").(0.123)).to eq("12%") 8 | expect(D3.format("($.2f").(-3.5)).to eq("($3.50)") 9 | expect(D3.format("+20").(42)).to eq(" +42") 10 | expect(D3.format(".^20").(42)).to eq(".........42.........") 11 | expect(D3.format(".2s").(42e6)).to eq("42M") 12 | expect(D3.format("#x").(48879)).to eq("0xbeef") 13 | expect(D3.format(",.2r").(4223)).to eq("4,200") 14 | end 15 | 16 | it "d3.format_prefix" do 17 | f = D3.format_prefix(",.0", 1e-6) 18 | expect(f.(0.0042)).to eq("4,200µ") 19 | expect(f.(0.00042)).to eq("420µ") 20 | end 21 | 22 | it "d3.format_specifier" do 23 | expect(D3.format_specifier("s")).to be_instance_of(D3::FormatSpecifier) 24 | expect(D3.format_specifier("s").to_h).to eq({ 25 | fill: " ", 26 | align: ">", 27 | sign: "-", 28 | symbol: "", 29 | zero: false, 30 | width: nil, 31 | comma: false, 32 | precision: nil, 33 | type: "s", 34 | }) 35 | s = D3.format_specifier("f") 36 | s.precision = D3.precision_fixed(0.01) 37 | f = D3.format(s) 38 | expect(f.(42)).to eq("42.00") 39 | end 40 | 41 | it "d3.precision_fixed" do 42 | expect(D3.precision_fixed(0.5)).to eq(1) 43 | end 44 | 45 | it "d3.precision_prefix" do 46 | expect(D3.precision_prefix(1e5, 1.3e6)).to eq(1) 47 | end 48 | 49 | it "d3.precision_round" do 50 | expect(D3.precision_round(0.01, 1.01)).to eq(3) 51 | end 52 | 53 | let(:pl) {{ 54 | decimal: ",", 55 | thousands: ".", 56 | grouping: [3], 57 | currency: ["", "zł"], 58 | }} 59 | let(:us) {{ 60 | decimal: ".", 61 | thousands: ",", 62 | grouping: [3], 63 | currency: ["$", ""], 64 | }} 65 | it "d3.format_locale" do 66 | locale = D3.format_locale(pl) 67 | expect(locale).to be_instance_of(D3::FormatLocale) 68 | expect(locale.format("$.2f").(-3.5)).to eq("-3,50zł") 69 | expect(locale.format("$.2f").(123456.789)).to eq("123456,79zł") 70 | expect(locale.format(",.2r").(4223)).to eq("4.200") 71 | expect(locale.format_prefix(",.0", 1e-3).(123456.789012)).to eq("123.456.789m") 72 | end 73 | 74 | # This test affects global state, so we must be sure to restore it 75 | it "d3.format_default_locale" do 76 | expect(D3.format(",.2r").(4223)).to eq("4,200") 77 | 78 | locale_pl = D3.format_default_locale(pl) 79 | expect(D3.format(",.2r").(4223)).to eq("4.200") 80 | expect(locale_pl.format(",.2r").(4223)).to eq("4.200") 81 | 82 | locale_us = D3.format_default_locale(us) 83 | expect(D3.format(",.2r").(4223)).to eq("4,200") 84 | expect(locale_pl.format(",.2r").(4223)).to eq("4.200") 85 | expect(locale_us.format(",.2r").(4223)).to eq("4,200") 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/histograms_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-array - histograms" do 2 | it "d3.histogram" do 3 | expect(D3.histogram).to be_a(D3::Histogram) 4 | end 5 | 6 | describe "histogram.thresholds" do 7 | let(:data) { (1..10).to_a } 8 | let(:h) { D3.histogram } 9 | it "block" do 10 | h.thresholds{|*args| D3.threshold_sturges(*args) } 11 | expect(h.(data)).to eq([ 12 | [1], 13 | [2,3], 14 | [4,5], 15 | [6,7], 16 | [8,9,10] 17 | ]) 18 | end 19 | 20 | it "symbol" do 21 | h.thresholds(:scott) 22 | expect(h.(data)).to eq([ 23 | [1,2,3,4], 24 | [5,6,7,8,9,10], 25 | ]) 26 | end 27 | 28 | it "number" do 29 | h.thresholds(2) 30 | expect(h.(data)).to eq([ 31 | [1, 2, 3, 4], 32 | [5, 6, 7, 8, 9, 10] 33 | ]) 34 | end 35 | end 36 | 37 | it "histogram.call" do 38 | h = D3.histogram 39 | expect(h.([1,2,1,2,1,3])).to eq([ 40 | [1,1,1], 41 | [], 42 | [2,2], 43 | [3], 44 | ]) 45 | end 46 | 47 | it "d3.threshold_freedman_diaconis" do 48 | data = (0..10).to_a 49 | expect(D3.threshold_freedman_diaconis(data,0,10)).to eq(3) 50 | end 51 | 52 | it "d3.threshold_scott" do 53 | data = (0..10).to_a 54 | expect(D3.threshold_scott(data,0,10)).to eq(2) 55 | end 56 | 57 | it "d3.threshold_sturges" do 58 | data = (0..10).to_a 59 | expect(D3.threshold_sturges(data,0,10)).to eq(5) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/html/d3.js: -------------------------------------------------------------------------------- 1 | ../../d3.js -------------------------------------------------------------------------------- /spec/html/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests 6 | 7 | 8 |
9 | <%= javascript_include_tag "html/d3" %> 10 | <%= javascript_include_tag @server.main %> 11 | 12 | 13 | -------------------------------------------------------------------------------- /spec/interpolate_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - interpolate" do 2 | let(:f) { D3.send(name, a, b) } 3 | let(:v) { f.(0.5) } 4 | let(:f2) { D3.send(name).(a, b) } 5 | let(:v2) { f2.(0.5) } 6 | 7 | describe "numbers" do 8 | let(:a) { 10 } 9 | let(:b) { 20 } 10 | 11 | describe "d3.interpolate_number" do 12 | let(:name) { :interpolate_number } 13 | it do 14 | expect(f.(0.3)).to eq(13) 15 | expect(f.(0.31)).to eq(13.1) 16 | expect(f.(0.31)).to eq(f2.(0.31)) 17 | end 18 | end 19 | 20 | describe "d3.interpolate_round" do 21 | let(:name) { :interpolate_round } 22 | it do 23 | expect(f.(0.3)).to eq(13) 24 | expect(f.(0.31)).to eq(13) 25 | expect(f.(0.31)).to eq(f2.(0.31)) 26 | end 27 | end 28 | end 29 | 30 | describe "d3.interpolate_string" do 31 | let(:a) { "300 12px sans-serif" } 32 | let(:b) { "500 36px Comic-Sans" } 33 | let(:name) { :interpolate_string } 34 | 35 | it do 36 | expect(f.(0.5)).to eq("400 24px Comic-Sans") 37 | expect(f.(0.5)).to eq(f2.(0.5)) 38 | end 39 | end 40 | 41 | describe "d3.interpolate_array" do 42 | let(:a) { [0, 200] } 43 | let(:b) { [10, 500] } 44 | let(:name) { :interpolate_array } 45 | 46 | it do 47 | expect(f.(0.5)).to eq([5, 350]) 48 | expect(f.(0.5)).to eq(f2.(0.5)) 49 | end 50 | end 51 | 52 | describe "d3.interpolate_date" do 53 | let(:a) { Time.parse("June 30, 2015 00:00:00 UTC") } 54 | let(:b) { Time.parse("December 31, 2016 12:00:00 UTC") } 55 | let(:name) { :interpolate_date } 56 | 57 | it do 58 | expect(f.(0.5)).to eq(Time.parse("March 31, 2016 06:00:00 UTC")) 59 | expect(f.(0.5)).to eq(f2.(0.5)) 60 | end 61 | end 62 | 63 | describe "colors" do 64 | let(:a) { "purple" } 65 | let(:b) { "orange" } 66 | 67 | describe "d3.interpolate_rgb" do 68 | let(:name) { :interpolate_rgb } 69 | it do 70 | expect(v).to eq("rgb(192, 83, 64)") 71 | expect(v).to eq(v2) 72 | end 73 | end 74 | 75 | describe "basis" do 76 | let(:f) { D3.send(name, [a, b]) } 77 | let(:f2) { D3.send(name).([a, b]) } 78 | 79 | describe "d3.interpolate_rgb_basis" do 80 | let(:name) { :interpolate_rgb_basis } 81 | it do 82 | expect(v).to eq("rgb(192, 83, 64)") 83 | expect(v).to eq(v2) 84 | end 85 | end 86 | 87 | describe "d3.interpolate_rgb_basis_closed" do 88 | let(:name) { :interpolate_rgb_basis_closed } 89 | it do 90 | expect(v).to eq("rgb(213, 110, 43)") 91 | expect(v).to eq(v2) 92 | end 93 | end 94 | end 95 | 96 | describe "d3.interpolate_hsl" do 97 | let(:name) { :interpolate_hsl } 98 | it do 99 | expect(v).to eq("rgb(192, 0, 34)") 100 | expect(v).to eq(v2) 101 | end 102 | end 103 | 104 | describe "d3.interpolate_hsl_long" do 105 | let(:name) { :interpolate_hsl_long } 106 | it do 107 | expect(v).to eq("rgb(0, 192, 158)") 108 | expect(v).to eq(v2) 109 | end 110 | end 111 | 112 | describe "d3.interpolate_lab" do 113 | let(:name) { :interpolate_lab } 114 | it do 115 | expect(v).to eq("rgb(197, 93, 91)") 116 | expect(v).to eq(v2) 117 | end 118 | end 119 | 120 | describe "d3.interpolate_hcl" do 121 | let(:name) { :interpolate_hcl } 122 | it do 123 | expect(v).to eq("rgb(236, 46, 83)") 124 | expect(v).to eq(v2) 125 | end 126 | end 127 | 128 | describe "d3.interpolate_hcl_long" do 129 | let(:name) { :interpolate_hcl_long } 130 | it do 131 | expect(v).to eq("rgb(0, 153, 169)") 132 | expect(v).to eq(v2) 133 | end 134 | end 135 | 136 | describe "d3.interpolate_cubehelix" do 137 | let(:name) { :interpolate_cubehelix } 138 | it do 139 | expect(v).to eq("rgb(255, 32, 68)") 140 | expect(v).to eq(v2) 141 | end 142 | end 143 | 144 | describe "d3.interpolate_cubehelix_long" do 145 | let(:name) { :interpolate_cubehelix_long } 146 | it do 147 | expect(v).to eq("rgb(0, 194, 158)") 148 | expect(v).to eq(v2) 149 | end 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /spec/line_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - line" do 2 | it "d3.line" do 3 | expect(D3.line).to be_instance_of(D3::LineGenerator) 4 | end 5 | 6 | let(:simple_data) { [[1,2],[3,5],[4,9]] } 7 | let(:extended_data) { [[1,2],[3,5],[4,9],[6,10],[7,8]] } 8 | 9 | it "basics" do 10 | expect(D3.line.(simple_data)).to eq("M1,2L3,5L4,9") 11 | end 12 | 13 | it "curve" do 14 | expect(D3.line.curve).to be_instance_of(D3::Curve) 15 | expect(D3.line.curve(D3.curve_natural).(simple_data)).to eq(%W[ 16 | M1,2 17 | C1.75,2.9166666666666665,2.5,3.833333333333333,3,5 18 | C3.5,6.166666666666667,3.75,7.583333333333334,4,9 19 | ].join) 20 | end 21 | 22 | it "x/y accessors" do 23 | line = D3.line 24 | line.x{|(x,y)| x*10}.y{|(x,y)| y*100} 25 | expect(line.x.([23,42])).to eq(230) 26 | expect(line.y.([23,42])).to eq(4200) 27 | expect(line.(simple_data)).to eq("M10,200L30,500L40,900") 28 | end 29 | 30 | it ".x constant" do 31 | line = D3.line.x(10) 32 | expect(line.(simple_data)).to eq("M10,2L10,5L10,9") 33 | expect(line.x.(42)).to eq(10) 34 | line.x = 20 35 | expect(line.x.(42)).to eq(20) 36 | end 37 | 38 | it ".y constant" do 39 | line = D3.line.y(10) 40 | expect(line.(simple_data)).to eq("M1,10L3,10L4,10") 41 | expect(line.y.(42)).to eq(10) 42 | line.y = 20 43 | expect(line.y.(42)).to eq(20) 44 | end 45 | 46 | it ".defined" do 47 | expect(D3.line.defined(false).(simple_data)).to eq(nil) 48 | expect(D3.line.defined{false}.(simple_data)).to eq(nil) 49 | expect(D3.line.defined(true).(simple_data)).to eq("M1,2L3,5L4,9") 50 | expect(D3.line.defined{true}.(simple_data)).to eq("M1,2L3,5L4,9") 51 | expect(D3.line.(extended_data)).to eq("M1,2L3,5L4,9L6,10L7,8") 52 | expect(D3.line.defined{|(x,y)| x.odd?}.(extended_data)).to eq("M1,2L3,5M7,8Z") 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/map_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-collections - maps" do 2 | it "d3.map" do 3 | expect(D3.map).to be_instance_of(D3::Map) 4 | end 5 | 6 | it "d3.has?" do 7 | expect(D3.map().has?("1")).to eq(false) 8 | expect(D3.map(["a","b"]).has?("0")).to eq(true) 9 | expect(D3.map(["a","b"]).has?(0)).to eq(true) 10 | expect(D3.map(["a","b"]).has?(2)).to eq(false) 11 | expect(D3.map(["a","b"]).has?("a")).to eq(false) 12 | end 13 | 14 | it "d3.get" do 15 | a = D3.map(["a", "b"]) 16 | expect(nil).to eq(nil) 17 | expect(a[0]).to eq("a") 18 | expect(a[2]).to eq(nil) 19 | expect(a.get(0)).to eq("a") 20 | expect(a.get(2)).to eq(nil) 21 | end 22 | 23 | it "d3.set" do 24 | a = D3.map.set("foo", 1).set("bar", 2) 25 | a["baz"] = 3 26 | expect(a["foo"]).to eq(1) 27 | expect(a["bar"]).to eq(2) 28 | expect(a["baz"]).to eq(3) 29 | end 30 | 31 | it "map.remove" do 32 | a = D3.map.set("a", 1).set("b", 2).remove("a").set("c", 3) 33 | expect(a.keys).to eq(["b", "c"]) 34 | end 35 | 36 | it "map.clear" do 37 | a = D3.map.set("a", 1).set("b", 2).clear.set("c", 3) 38 | expect(a.keys).to eq(["c"]) 39 | end 40 | 41 | it "map.keys" do 42 | a = D3.map.set("foo", 1).set("bar", 2) 43 | expect(a.keys).to eq(["foo", "bar"]) 44 | end 45 | 46 | it "map.values" do 47 | a = D3.map.set("foo", 1).set("bar", 2) 48 | expect(a.values).to eq([1, 2]) 49 | end 50 | 51 | it "map.entries" do 52 | a = D3.map.set("foo", 1).set("bar", 2) 53 | expect(a.entries).to eq([["foo", 1], ["bar", 2]]) 54 | end 55 | 56 | it "map.to_h" do 57 | a = D3.map.set("foo", 1).set("bar", 2) 58 | expect(a.to_h).to eq({"foo" => 1, "bar" => 2}) 59 | end 60 | 61 | it "map.each" do 62 | called = [] 63 | a = D3.map.set("foo", 1).set("bar", 2) 64 | a.each do |v,k| 65 | called << [k,v] 66 | end 67 | expect(called).to eq([["foo",1], ["bar",2]]) 68 | 69 | end 70 | 71 | it "map.empty" do 72 | expect(D3.map).to be_empty 73 | expect(D3.map(["a"])).to_not be_empty 74 | end 75 | 76 | it "map.size" do 77 | expect(D3.map.size).to eq(0) 78 | expect(D3.map(["a"]).size).to eq(1) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/misc_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 misc methods" do 2 | it "d3.version" do 3 | # opal-d3 is highly compatible with various d3 minor updates, 4 | # but we're only testing against one bundled here 5 | expect(D3.version).to eq("4.13.0") 6 | end 7 | 8 | it "d3.namespace" do 9 | expect(D3.namespace("svg:text")).to eq({"space"=>"http://www.w3.org/2000/svg", "local"=>"text"}) 10 | end 11 | 12 | it "d3.namespaces" do 13 | expect(D3.namespaces).to eq({ 14 | "svg"=>"http://www.w3.org/2000/svg", 15 | "xhtml"=>"http://www.w3.org/1999/xhtml", 16 | "xlink"=>"http://www.w3.org/1999/xlink", 17 | "xml"=>"http://www.w3.org/XML/1998/namespace", 18 | "xmlns"=>"http://www.w3.org/2000/xmlns/" 19 | }) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/nest_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-collections - nests" do 2 | it "d3.nest" do 3 | expect(D3.nest).to be_instance_of(D3::Nest) 4 | end 5 | 6 | it "zero levels" do 7 | data = [61, 35, 24, 37, 9, 20, 42, 55, 58, 57] 8 | nest = D3.nest.sort_values(:ascending) 9 | expect(nest.entries(data)).to eq(data) 10 | expect(nest.map(data)).to eq(data) 11 | end 12 | 13 | it "one level" do 14 | data = [61, 35, 24, 37, 9, 20, 42, 55, 58, 57] 15 | nest = D3.nest.key{|x| (x / 10).floor}.sort_keys(:ascending).sort_values(:descending) 16 | expect(nest.map(data).to_h).to eq( 17 | {"6"=>[61], "3"=>[37, 35], "2"=>[24, 20], "0"=>[9], "4"=>[42], "5"=>[58, 57, 55]} 18 | ) 19 | expect(nest.entries(data)).to eq([ 20 | ["0", [9]], 21 | ["2", [24, 20]], 22 | ["3", [37, 35]], 23 | ["4", [42]], 24 | ["5", [58, 57, 55]], 25 | ["6", [61]], 26 | ]) 27 | o = nest.object(data) 28 | expect(`JSON.stringify(#{o})`).to eq( 29 | '{"0":[9],"2":[24,20],"3":[37,35],"4":[42],"5":[58,57,55],"6":[61]}' 30 | ) 31 | end 32 | 33 | it "one level and rollup" do 34 | data = [61, 35, 24, 37, 9, 20, 42, 55, 58, 57] 35 | # rollup and sorting got fixed in 4.10 36 | nest = D3.nest.key{|x| (x / 10).floor}.sort_keys(:ascending) 37 | .sort_values(:descending) 38 | .rollup{|*x| x.join(":")} 39 | expect(nest.map(data).to_h).to eq( 40 | {"6"=>"61", "3"=>"37:35", "2"=>"24:20", "0"=>"9", "4"=>"42", "5"=>"58:57:55"} 41 | ) 42 | expect(nest.entries(data)).to eq([ 43 | ["0", "9"], 44 | ["2", "24:20"], 45 | ["3", "37:35"], 46 | ["4", "42"], 47 | ["5", "58:57:55"], 48 | ["6", "61"] 49 | ]) 50 | end 51 | 52 | it "three levels" do 53 | data = (1..25).to_a + (80..85).to_a 54 | nest = D3.nest.key{|x| "d#{ x % 2}" }.sort_keys(:ascending) 55 | .key{|x| "t#{ x % 3}" }.sort_keys(:descending) 56 | .key{|x| "v#{ x % 5}" }.sort_keys(:ascending) 57 | .sort_values(:descending) 58 | expect(nest.entries(data)).to eq([ 59 | ["d0", [["t2", [["v0", [80, 20]], ["v2", [2]], ["v3", [8]], ["v4", [14]]]], 60 | ["t1", [["v0", [10]], ["v1", [16]], ["v2", [82, 22]], ["v4", [4]]]], 61 | ["t0", [["v1", [6]], ["v2", [12]], ["v3", [18]], ["v4", [84, 24]]]]]], 62 | ["d1", [["t2", [["v0", [5]], ["v1", [11]], ["v2", [17]], ["v3", [83, 23]]]], 63 | ["t1", [["v0", [85, 25]], ["v1", [1]], ["v2", [7]], ["v3", [13]], ["v4", [19]]]], 64 | ["t0", [["v0", [15]], ["v1", [81, 21]], ["v3", [3]], ["v4", [9]]]]]] 65 | ]) 66 | expect(nest.map(data).inspect).to eq( 67 | %Q[###[1], "v2"=>[7], "v3"=>[13], "v4"=>[19], "v0"=>[85, 25]}>, "t0"=>#[3], "v4"=>[9], "v0"=>[15], "v1"=>[81, 21]}>, "t2"=>#[5], "v1"=>[11], "v2"=>[17], "v3"=>[83, 23]}>}>, "d0"=>##[2], "v3"=>[8], "v4"=>[14], "v0"=>[80, 20]}>, "t1"=>#[4], "v0"=>[10], "v1"=>[16], "v2"=>[82, 22]}>, "t0"=>#[6], "v2"=>[12], "v3"=>[18], "v4"=>[84, 24]}>}>}>] 68 | ) 69 | end 70 | 71 | it "three levels and rollup" do 72 | data = (1..25).to_a + (80..85).to_a 73 | nest = D3.nest.key{|x| "d#{ x % 2}" }.sort_keys(:ascending) 74 | .key{|x| "t#{ x % 3}" }.sort_keys(:descending) 75 | .key{|x| "v#{ x % 5}" }.sort_keys(:ascending) 76 | .rollup{|*x| x.join(":")} 77 | expect(nest.entries(data)).to eq([ 78 | ["d0", [["t2", [["v0", "20:80"], ["v2", "2"], ["v3", "8"], ["v4", "14"]]], 79 | ["t1", [["v0", "10"], ["v1", "16"], ["v2", "22:82"], ["v4", "4"]]], 80 | ["t0", [["v1", "6"], ["v2", "12"], ["v3", "18"], ["v4", "24:84"]]]]], 81 | ["d1", [["t2", [["v0", "5"], ["v1", "11"], ["v2", "17"], ["v3", "23:83"]]], 82 | ["t1", [["v0", "25:85"], ["v1", "1"], ["v2", "7"], ["v3", "13"], ["v4", "19"]]], 83 | ["t0", [["v0", "15"], ["v1", "21:81"], ["v3", "3"], ["v4", "9"]]]]] 84 | ]) 85 | expect(nest.map(data).inspect).to eq( 86 | %Q[###"1", "v2"=>"7", "v3"=>"13", "v4"=>"19", "v0"=>"25:85"}>, "t0"=>#"3", "v4"=>"9", "v0"=>"15", "v1"=>"21:81"}>, "t2"=>#"5", "v1"=>"11", "v2"=>"17", "v3"=>"23:83"}>}>, "d0"=>##"2", "v3"=>"8", "v4"=>"14", "v0"=>"20:80"}>, "t1"=>#"4", "v0"=>"10", "v1"=>"16", "v2"=>"22:82"}>, "t0"=>#"6", "v2"=>"12", "v3"=>"18", "v4"=>"24:84"}>}>}>] 87 | ) 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/objects_spec.rb: -------------------------------------------------------------------------------- 1 | # These operate on JS native objects, and might not be that useful from Opal 2 | # Not converting these to work on ruby objects, as ruby standard library 3 | # already has all relevant methods 4 | describe "d3-collections - objects" do 5 | let(:object) { `{a: 1, b: 2, c: 3}` } 6 | 7 | it "d3.keys" do 8 | expect(D3.keys(object)).to eq(["a", "b", "c"]) 9 | end 10 | 11 | it "d3.values" do 12 | expect(D3.values(object)).to eq([1, 2, 3]) 13 | end 14 | 15 | it "d3.entries" do 16 | expect(D3.entries(object)).to eq([ 17 | ["a",1], 18 | ["b",2], 19 | ["c",3], 20 | ]) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/ordinal_scale_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 ordinal scale" do 2 | it "d3.scale_ordinal" do 3 | expect(D3.scale_ordinal).to be_instance_of(D3::OrdinalScale) 4 | end 5 | 6 | let(:d) { ("a".."z").to_a } 7 | let(:r) { D3.scheme_category_10 } 8 | let(:colors) { D3.scale_ordinal.domain(d).range(r) } 9 | 10 | it "basics" do 11 | expect(colors.domain).to eq(d) 12 | expect(colors.range).to eq(r) 13 | expect(colors.("c")).to eq("#2ca02c") 14 | expect(colors.("z")).to eq("#8c564b") 15 | # default implicit 16 | expect(colors.("X")).to eq("#e377c2") 17 | expect(colors.("Y")).to eq("#7f7f7f") 18 | expect(colors.("X")).to eq("#e377c2") 19 | end 20 | 21 | it "unknown" do 22 | expect(D3.scale_implicit).to eq(:implicit) 23 | expect(colors.unknown).to eq(:implicit) 24 | 25 | colors.unknown("#ffffff") 26 | expect(colors.unknown).to eq("#ffffff") 27 | expect(colors.("c")).to eq("#2ca02c") 28 | expect(colors.("C")).to eq("#ffffff") 29 | 30 | colors.unknown(:implicit) 31 | expect(colors.unknown).to eq(:implicit) 32 | expect(colors.("c")).to eq("#2ca02c") 33 | expect(colors.("C")).to eq("#e377c2") 34 | end 35 | 36 | it "copy" do 37 | cc = colors.copy.domain([1,2,3]) 38 | expect(colors.domain).to eq(d) 39 | expect(cc.domain).to eq([1,2,3]) 40 | expect(colors.range).to eq(r) 41 | expect(cc.range).to eq(r) 42 | end 43 | 44 | it "d3.scheme_category_10" do 45 | expect(D3.scheme_category_10).to eq(["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/path_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - path" do 2 | it "d3.path" do 3 | expect(D3.path).to be_instance_of(D3::Path) 4 | end 5 | 6 | it "path.move_to" do 7 | expect(D3.path.move_to(10,20).to_s).to eq("M10,20") 8 | expect(D3.path.move_to(10,20).move_to(30,40).to_s).to eq("M10,20M30,40") 9 | end 10 | 11 | it "path.line_to" do 12 | expect(D3.path.line_to(30,40).to_s).to eq("L30,40") 13 | expect(D3.path.line_to(10,20).line_to(30,40).to_s).to eq("L10,20L30,40") 14 | end 15 | 16 | it "path.close_path" do 17 | expect(D3.path.line_to(10,20).close_path.to_s).to eq("L10,20Z") 18 | expect(D3.path.line_to(10,20).close_path.line_to(30,40).close_path.to_s).to eq("L10,20ZL30,40Z") 19 | end 20 | 21 | it "path.rect" do # (x,y,w,h) not (x1,y1,x2,y2) 22 | expect(D3.path.rect(10,20,30,40).to_s).to eq("M10,20h30v40h-30Z") 23 | expect(D3.path.rect(10,20,30,40).rect(50,60,70,80).to_s).to eq("M10,20h30v40h-30ZM50,60h70v80h-70Z") 24 | end 25 | 26 | it "path.quadratic_curve_to" do 27 | expect(D3.path.quadratic_curve_to(10,20,30,40).to_s).to eq("Q10,20,30,40") 28 | expect(D3.path.quadratic_curve_to(10,20,30,40) 29 | .quadratic_curve_to(50,60,70,80).to_s).to eq("Q10,20,30,40Q50,60,70,80") 30 | end 31 | 32 | it "path.bezier_curve_to" do 33 | expect(D3.path.bezier_curve_to(10,20,30,40,50,60).to_s).to eq("C10,20,30,40,50,60") 34 | expect(D3.path.bezier_curve_to(10,20,30,40,50,60) 35 | .bezier_curve_to(70,80,90,100,110,120).to_s).to eq( 36 | "C10,20,30,40,50,60C70,80,90,100,110,120") 37 | end 38 | 39 | it "path.arc" do 40 | expect(D3.path.arc(100,100,50,90,225).to_s).to eq( 41 | "M77.59631919354149,144.6998331800279"+ 42 | "A50,50,0,1,1,122.40368080645851,55.300166819972105"+ 43 | "A50,50,0,1,1,77.59631919354149,144.6998331800279" 44 | ) 45 | expect(D3.path.arc(100,100,50,90,225,false) 46 | .arc(100,100,50,90,225,true).to_s).to eq( 47 | "M77.59631919354149,144.6998331800279"+ 48 | "A50,50,0,1,1,122.40368080645851,55.300166819972105" + 49 | "A50,50,0,1,1,77.59631919354149,144.6998331800279" + 50 | "A50,50,0,1,0,118.36596838651226,53.495256099773734" 51 | ) 52 | end 53 | 54 | it "path.arc_to" do 55 | # To be honest it's not clear to me how that's supposed to work exactly 56 | # but we're just doing bindings here 57 | expect(D3.path 58 | .move_to(10,10) 59 | .arc_to(20,20,30,10,10) 60 | .to_s).to eq( 61 | "M10,10"+ 62 | "L12.928932188134526,12.928932188134526"+ 63 | "A10,10,0,0,0,27.071067811865476,12.928932188134526") 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/pie_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - pie" do 2 | let(:generated) { pie.(data) } 3 | let(:rounded) { 4 | generated.map{|arc| arc.map{|k,v| [k, v.is_a?(Float) ? v.round(2) : v] }.to_h} 5 | } 6 | let(:data) { [1, 3, 4] } 7 | let(:pie) { D3.pie } 8 | 9 | it "d3.pie" do 10 | expect(D3.pie).to be_instance_of(D3::PieGenerator) 11 | end 12 | 13 | describe "basics" do 14 | it do 15 | expect(rounded).to eq([ 16 | {data: 1, index: 2, value: 1, start_angle: 5.50, end_angle: 6.28, pad_angle: 0}, 17 | {data: 3, index: 1, value: 3, start_angle: 3.14, end_angle: 5.50, pad_angle: 0}, 18 | {data: 4, index: 0, value: 4, start_angle: 0.00, end_angle: 3.14, pad_angle: 0}, 19 | ]) 20 | end 21 | end 22 | 23 | describe "pie.start_angle" do 24 | it do 25 | expect(pie.start_angle.()).to eq(0) 26 | pie.start_angle(2) 27 | expect(pie.start_angle.()).to eq(2) 28 | expect(rounded).to eq([ 29 | {data: 1, index: 2, value: 1, start_angle: 5.75, end_angle: 6.28, pad_angle: 0}, 30 | {data: 3, index: 1, value: 3, start_angle: 4.14, end_angle: 5.75, pad_angle: 0}, 31 | {data: 4, index: 0, value: 4, start_angle: 2.00, end_angle: 4.14, pad_angle: 0}, 32 | ]) 33 | end 34 | end 35 | 36 | describe "pie.end_angle" do 37 | it do 38 | expect(pie.end_angle.()).to eq(2*Math::PI) 39 | pie.end_angle(4) 40 | expect(pie.end_angle.()).to eq(4) 41 | expect(rounded).to eq([ 42 | {data: 1, index: 2, value: 1, start_angle: 3.50, end_angle: 4.00, pad_angle: 0}, 43 | {data: 3, index: 1, value: 3, start_angle: 2.00, end_angle: 3.50, pad_angle: 0}, 44 | {data: 4, index: 0, value: 4, start_angle: 0.00, end_angle: 2.00, pad_angle: 0}, 45 | ]) 46 | end 47 | end 48 | 49 | describe "pie.pad_angle" do 50 | it do 51 | expect(pie.pad_angle.()).to eq(0) 52 | pie.pad_angle(0.1) 53 | expect(pie.pad_angle.()).to eq(0.1) 54 | expect(rounded).to eq([ 55 | {data: 1, index: 2, value: 1, start_angle: 5.44, end_angle: 6.28, pad_angle: 0.1}, 56 | {data: 3, index: 1, value: 3, start_angle: 3.09, end_angle: 5.44, pad_angle: 0.1}, 57 | {data: 4, index: 0, value: 4, start_angle: 0.00, end_angle: 3.09, pad_angle: 0.1}, 58 | ]) 59 | end 60 | end 61 | 62 | describe "accessors" do 63 | let(:data) {[ 64 | ["a", 10], 65 | ["b", 5], 66 | ["d", 15], 67 | ["c", 20], 68 | ]} 69 | let(:pie) { D3.pie.value{|d| d[1]} } 70 | let(:order) { 71 | generated 72 | .sort_by{|arc| arc[:index] } 73 | .map{|arc| arc.select{|k,v| k =~ /data|value/ }} 74 | } 75 | it "pie.value" do 76 | expect(order).to eq([ 77 | {"data"=>["c", 20], "value"=>20}, 78 | {"data"=>["d", 15], "value"=>15}, 79 | {"data"=>["a", 10], "value"=>10}, 80 | {"data"=>["b", 5], "value"=> 5}, 81 | ]) 82 | end 83 | 84 | it "pie.sort" do 85 | pie.sort{|a,b| a <=> b} 86 | expect(order).to eq([ 87 | {"data"=>["a", 10], "value"=>10}, 88 | {"data"=>["b", 5], "value"=> 5}, 89 | {"data"=>["c", 20], "value"=>20}, 90 | {"data"=>["d", 15], "value"=>15}, 91 | ]) 92 | end 93 | 94 | it "pie.sort - nil" do 95 | pie.sort(nil) 96 | expect(order).to eq([ 97 | {"data"=>["a", 10], "value"=>10}, 98 | {"data"=>["b", 5], "value"=> 5}, 99 | {"data"=>["d", 15], "value"=>15}, 100 | {"data"=>["c", 20], "value"=>20}, 101 | ]) 102 | end 103 | 104 | it "pie.sort_values" do 105 | pie.sort_values{|a,b| a <=> b} 106 | expect(order).to eq([ 107 | {"data"=>["b", 5], "value"=> 5}, 108 | {"data"=>["a", 10], "value"=>10}, 109 | {"data"=>["d", 15], "value"=>15}, 110 | {"data"=>["c", 20], "value"=>20}, 111 | ]) 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /spec/point_scale_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - point scale" do 2 | it "d3.scale_point" do 3 | expect(D3.scale_point).to be_instance_of(D3::PointScale) 4 | end 5 | 6 | let(:d) { ("a".."f").to_a } 7 | it "basics" do 8 | scale = D3.scale_point.domain(d) 9 | expect(d.map{|v| scale.(v).round(2)}).to eq([0, 0.2, 0.4, 0.6, 0.8, 1]) 10 | expect(scale.bandwidth).to eq(0) 11 | expect(scale.step).to eq(0.2) 12 | expect(scale.("z")).to eq(nil) 13 | end 14 | 15 | it "copy" do 16 | scale = D3.scale_point.domain(d) 17 | sc = scale.copy.range([10,20]) 18 | expect(scale.domain).to eq(d) 19 | expect(sc.domain).to eq(d) 20 | expect(scale.range).to eq([0,1]) 21 | expect(sc.range).to eq([10,20]) 22 | end 23 | 24 | it "padding" do 25 | scale = D3.scale_point.domain(d).range([0,100]) 26 | expect(scale.padding).to eq(0) 27 | 28 | scale.padding(0.5) 29 | expect(scale.padding).to eq(0.5) 30 | expect(d.map{|v| scale.(v).round(2)}).to eq([8.33, 25, 41.67, 58.33, 75, 91.67]) 31 | end 32 | 33 | it "align" do 34 | scale = D3.scale_point.domain(d).range([0,100]) 35 | scale.padding(0.5) 36 | 37 | expect(scale.align).to eq(0.5) 38 | expect(d.map{|v| scale.(v).round(2)}).to eq([8.33, 25, 41.67, 58.33, 75, 91.67]) 39 | 40 | scale.align(0) 41 | expect(scale.align).to eq(0) 42 | expect(d.map{|v| scale.(v).round(2)}).to eq([0, 16.67, 33.33, 50, 66.67, 83.33]) 43 | 44 | scale.align(1) 45 | expect(scale.align).to eq(1) 46 | expect(d.map{|v| scale.(v).round(2)}).to eq([16.67, 33.33, 50, 66.67, 83.33, 100]) 47 | end 48 | 49 | it "round" do 50 | scale = D3.scale_point.domain(d).range([0,100]) 51 | expect(scale.round).to eq(false) 52 | scale.range_round([0,1000]).padding(1) 53 | expect(scale.range).to eq([0,1000]) 54 | expect(scale.round).to eq(true) 55 | # This rounding is somewhat weird 56 | expect(d.map{|v| scale.(v)}).to eq([145, 287, 429, 571, 713, 855]) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/polygon_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - polygon" do 2 | let(:square) { 3 | [[10,10], [40,10], [40,40], [10,40]] 4 | } 5 | let(:triangle) { 6 | [[100,0], [160,0], [130, 40]] 7 | } 8 | let(:random_points) {[ 9 | [6, 39], [59, 86], [96, 20], [56, 5], [96, 98], 10 | [70, 4], [47, 48], [77, 15], [2, 60], [41, 28], 11 | [56, 59], [10, 15], [79, 12], [82, 97], [71, 14], 12 | [18, 30], [64, 86], [35, 83], [49, 73], [78, 12], 13 | ]} 14 | 15 | # signed area 16 | it "d3.polygon_area" do 17 | expect(D3.polygon_area(square)).to eq(-900) 18 | expect(D3.polygon_area(triangle)).to eq(-1200) 19 | end 20 | 21 | it "d3.polygon_centroid" do 22 | expect(D3.polygon_centroid(square)).to eq([25, 25]) 23 | expect(D3.polygon_centroid(triangle)).to eq([130, 13.333333333333334]) 24 | end 25 | 26 | it "d3.polygon_length" do 27 | expect(D3.polygon_length(square)).to eq(120) 28 | expect(D3.polygon_length(triangle)).to eq(160) 29 | end 30 | 31 | it "d3.polygon_contains?" do 32 | expect(D3.polygon_contains?(square, [20, 20])).to eq(true) 33 | expect(D3.polygon_contains?(square, [50, 20])).to eq(false) 34 | # Sometimes corners considered "inside" 35 | expect(D3.polygon_contains?(square, [10, 10])).to eq(true) 36 | end 37 | 38 | it "d3.polygon_hull" do 39 | hull = D3.polygon_hull(random_points) 40 | hull.each do |pt| 41 | expect(random_points).to include(pt) 42 | end 43 | 44 | random_points.each do |pt| 45 | # Sometimes corners not considered "inside" 46 | expect(D3.polygon_contains?(hull, pt) || hull.include?(pt)).to eq(true) 47 | end 48 | 49 | expect(D3.polygon_hull([[10,10], [20,20]])).to eq(nil) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/quadtree_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - quadtree" do 2 | let(:a) { [1,2,"a"] } 3 | let(:a2) { [1,2,"a"] } 4 | let(:b) { [3,4,"b"] } 5 | let(:c) { [5,6,"c"] } 6 | let(:d) { [7,8,"c"] } 7 | let(:e) { [3,4,"e"] } 8 | 9 | it "d3.quadtree" do 10 | expect(D3.quadtree).to be_instance_of(D3::QuadTree) 11 | end 12 | 13 | it "quadtree.size" do 14 | q = D3.quadtree 15 | expect(q.size).to eq(0) 16 | expect(q.add(a).size).to eq(1) 17 | expect(q.add(b).size).to eq(2) 18 | end 19 | 20 | it "quadtree.remove" do 21 | q = D3.quadtree 22 | expect(q.size).to eq(0) 23 | expect(q.add(a).add(b).size).to eq(2) 24 | expect(q.remove(a2).size).to eq(2) 25 | expect(q.remove(a).size).to eq(1) 26 | expect(q.remove(c).size).to eq(1) 27 | end 28 | 29 | it "quadtree.remove_all" do 30 | q = D3.quadtree([a,b,c]) 31 | expect(q.remove_all([b,d]).data).to eq([a,c]) 32 | end 33 | 34 | it "quadtree.data" do 35 | q = D3.quadtree 36 | expect(q.data).to eq([]) 37 | expect(q.add(a).data).to eq([a]) 38 | expect(q.add_all([b,c]).data).to eq([a,b,c]) 39 | end 40 | 41 | it "quadtree.find" do 42 | q = D3.quadtree 43 | expect(q.find(1,1)).to eq(nil) 44 | q.add([1,2,"foo"]) 45 | expect(q.find(3,3)).to eq([1,2,"foo"]) 46 | expect(q.find(3,3,1)).to eq(nil) 47 | end 48 | 49 | it "quadtree.copy" do 50 | q1 = D3.quadtree.add(a) 51 | q2 = q1.copy.add(b) 52 | q1.add(c) 53 | expect(q1.data).to eq([a,c]) 54 | expect(q2.data).to eq([a,b]) 55 | end 56 | 57 | it "quadtree.visit" do 58 | q = D3.quadtree([a,b,c,e]) 59 | called = [] 60 | q.visit do |node, x0, y0, x1, y1| 61 | called << [x0, y0, x1, y1, node.internal?, node.leaf?, node.data, !!node.next, node.children && node.children.map{|x| !!x}] 62 | false 63 | end 64 | expect(called).to eq([ 65 | [1, 2, 5, 6, true, false, nil, false, [true, false, false, true]], 66 | [1, 2, 3, 4, false, true, [1, 2, "a"], false, nil], 67 | [3, 4, 5, 6, true, false, nil, false, [true, false, false, true]], 68 | [3, 4, 4, 5, false, true, [3, 4, "e"], true, nil], 69 | [4, 5, 5, 6, false, true, [5, 6, "c"], false, nil], 70 | ]) 71 | end 72 | 73 | it "quadtree.visit_after" do 74 | q = D3.quadtree([a,b,c,e]) 75 | called = [] 76 | q.visit_after do |node, x0, y0, x1, y1| 77 | called << [x0, y0, x1, y1, node.internal?, node.leaf?, node.data, !!node.next, node.children && node.children.map{|x| !!x}] 78 | false 79 | end 80 | expect(called).to eq([ 81 | [1, 2, 3, 4, false, true, [1, 2, "a"], false, nil], 82 | [3, 4, 4, 5, false, true, [3, 4, "e"], true, nil], 83 | [4, 5, 5, 6, false, true, [5, 6, "c"], false, nil], 84 | [3, 4, 5, 6, true, false, nil, false, [true, false, false, true]], 85 | [1, 2, 5, 6, true, false, nil, false, [true, false, false, true]], 86 | ]) 87 | end 88 | 89 | it "quadtree.root" do 90 | q = D3.quadtree([a,b,c]) 91 | root = q.root 92 | expect(root).to be_instance_of(D3::Quad) 93 | expect(root).to be_internal 94 | end 95 | 96 | it "quadtree.extent" do 97 | q = D3.quadtree([a,b,c]) 98 | expect(q.extent).to eq([[1, 2], [5, 6]]) 99 | expect(q.extent([[0,0],[10,1]]).extent([[-1,-1],[1,1]]).extent).to eq([[-3, -10], [13, 6]]) 100 | end 101 | 102 | it "quadtree.cover" do 103 | q = D3.quadtree([a,b,c]) 104 | expect(q.extent).to eq([[1, 2], [5, 6]]) 105 | expect(q.cover(0,0).cover(-2,10).extent).to eq([[-11, -2], [5, 14]]) 106 | end 107 | 108 | it "quadtree.x/y defaults" do 109 | q = D3.quadtree([a,b,c]) 110 | expect(q.x.(c)).to eq(5) 111 | expect(q.y.(c)).to eq(6) 112 | end 113 | 114 | it "quadtree.x/y constructor" do 115 | q = D3.quadtree([a,b,c], proc{|(x,y,n)| x*10}, proc{|(x,y,n)| y*10}) 116 | expect(q.x.(c)).to eq(50) 117 | expect(q.y.(c)).to eq(60) 118 | expect(q.extent).to eq([[10, 20], [74, 84]]) 119 | end 120 | 121 | it "quadtree.x/y" do 122 | q = D3.quadtree 123 | q.x{|(x,y,n)| y*100}.y{|(x,y,n)| x*100}.add_all([a,b,c]) 124 | expect(q.x.(c)).to eq(600) 125 | expect(q.y.(c)).to eq(500) 126 | expect(q.extent).to eq([[200, 100], [712, 612]]) 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /spec/quantile_scale_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - quantile scale" do 2 | it "d3.scale_quantile" do 3 | expect(D3.scale_quantile).to be_instance_of(D3::QuantileScale) 4 | end 5 | 6 | it "basics" do 7 | q = D3.scale_quantile.domain([0,10]).range(("a".."f").to_a) 8 | expect(q.(4)).to eq("c") 9 | 10 | expect(q.domain).to eq([0,10]) 11 | expect(q.range).to eq(("a".."f").to_a) 12 | expect(q.quantiles).to eq([1.6666666666666665, 3.333333333333333, 5, 6.666666666666666, 8.333333333333334]) 13 | expect(q.invert_extent("e")).to eq([6.666666666666666, 8.333333333333334]) 14 | end 15 | 16 | it "copy" do 17 | q = D3.scale_quantile.domain([1,5]).range(["x", "y"]) 18 | qc = q.copy.domain([0,20]) 19 | expect(qc.domain).to eq([0,20]) 20 | expect(qc.range).to eq(["x", "y"]) 21 | expect(q.domain).to eq([1,5]) 22 | expect(q.range).to eq(["x", "y"]) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/quantize_scale_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - quantize scale" do 2 | let(:color) { D3.scale_quantize.domain([0, 1]).range(["brown", "steelblue"]) } 3 | let(:width) { D3.scale_quantize.domain([10, 100]).range([1, 2, 4]) } 4 | 5 | it "d3.quantize_scale" do 6 | expect(D3.scale_quantize).to be_instance_of(D3::QuantizeScale) 7 | end 8 | 9 | it "basics - color" do 10 | expect(color.(0.49)).to eq("brown") 11 | expect(color.(0.51)).to eq("steelblue") 12 | expect(color.domain).to eq([0,1]) 13 | expect(color.range).to eq(["brown", "steelblue"]) 14 | end 15 | 16 | it "basics - width" do 17 | expect(width.(20)).to eq(1) 18 | expect(width.(50)).to eq(2) 19 | expect(width.(80)).to eq(4) 20 | expect(width.invert_extent(2)).to eq([40, 70]) 21 | end 22 | 23 | it ".nice / .copy" do 24 | s = D3.scale_quantize.domain([7, 109]).range([1, 2, 4]) 25 | sc = s.copy 26 | expect(s.nice().domain).to eq([0, 110]) 27 | expect(sc.domain).to eq([7, 109]) 28 | end 29 | 30 | it ".ticks" do 31 | expect(width.ticks).to eq([10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) 32 | expect(width.ticks(5)).to eq([20, 40, 60, 80, 100]) 33 | end 34 | 35 | it ".tick_format" do 36 | expect(width.tick_format.(30)).to eq("30") 37 | expect(width.tick_format(5).(30)).to eq("30") 38 | expect(width.tick_format(5, "+%").(30)).to eq("+3000%") 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/radial_area_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - radial area" do 2 | it "d3.radial_area" do 3 | expect(D3.radial_area).to be_instance_of(D3::RadialAreaGenerator) 4 | end 5 | 6 | let(:simple_data) {[ 7 | {year: 2007, value: 93.24}, 8 | {year: 2008, value: 95.35}, 9 | {year: 2009, value: 98.84}, 10 | {year: 2010, value: 99.92}, 11 | {year: 2011, value: 99.80}, 12 | {year: 2014, value: 99.47}, 13 | ]} 14 | 15 | describe "basics" do 16 | let(:radial_area) { 17 | D3.radial_area 18 | .angle{|d| (d[:year]-2000)/10 } 19 | .outer_radius{|d| d[:value] } 20 | .inner_radius(50) 21 | } 22 | it "basics" do 23 | expect(radial_area.(simple_data).gsub(/\d+\.\d+/){$&.to_f.round(2)}).to eq(%W[ 24 | M60.07,-71.31 25 | L68.4,-66.43 26 | L77.42,-61.44 27 | L84.08,-53.99 28 | L88.94,-45.27 29 | L98.02,-16.91 30 | L49.27,-8.5 31 | L44.56,-22.68 32 | L42.07,-27.02 33 | L39.17,-31.08 34 | L35.87,-34.84 35 | L32.21,-38.24 36 | Z 37 | ].join) 38 | end 39 | 40 | it "curve" do 41 | expect(radial_area.curve).to be_instance_of(D3::Curve) 42 | expect(radial_area.curve(D3.curve_natural).(simple_data).gsub(/\d+\.\d+/){$&.to_f.round(2)}).to eq(%W[ 43 | M60.07,-71.31C62.74,-69.61,65.42,-67.91,68.4,-66.43 44 | C71.38,-64.95,74.66,-63.68,77.42,-61.44 45 | C80.19,-59.2,82.45,-55.98,84.08,-53.99 46 | C85.71,-52,86.72,-51.24,88.94,-45.27 47 | C91.16,-39.29,94.59,-28.1,98.02,-16.91 48 | L49.27,-8.5 49 | C47.49,-14.1,45.72,-19.7,44.56,-22.68 50 | C43.4,-25.66,42.87,-26.01,42.07,-27.02 51 | C41.28,-28.02,40.23,-29.66,39.17,-31.08 52 | C38.1,-32.5,37.03,-33.68,35.87,-34.84 53 | C34.71,-35.99,33.46,-37.12,32.21,-38.24 54 | Z 55 | ].join) 56 | end 57 | 58 | 59 | it ".defined" do 60 | expect(radial_area.defined{|d| d[:year] != 2010}.(simple_data).gsub(/\d+\.\d+/){$&.to_f.round(2)}).to eq(%W[ 61 | M60.07,-71.31 62 | L68.4,-66.43 63 | L77.42,-61.44 64 | L39.17,-31.08 65 | L35.87,-34.84 66 | L32.21,-38.24 67 | Z 68 | M88.94,-45.27 69 | L98.02,-16.91 70 | L49.27,-8.5 71 | L44.56,-22.68 72 | Z 73 | ].join) 74 | end 75 | end 76 | 77 | describe "line generators" do 78 | let(:radial_area) { 79 | D3.radial_area 80 | .start_angle{|d| (d[:year]-2000)/20 } 81 | .end_angle{|d| (d[:year]-2000)/10 } 82 | .outer_radius{|d| d[:value] } 83 | .inner_radius(50) 84 | } 85 | let(:point) { {year: 2042, value: 117 } } 86 | 87 | it ".angle/.start_angle/.end_angle" do 88 | expect(radial_area.start_angle.(point)).to eq(2.1) 89 | expect(radial_area.end_angle.(point)).to eq(4.2) 90 | expect(radial_area.angle.(point)).to eq(2.1) 91 | end 92 | 93 | it ".radius/.inner_radius/.outer_radius" do 94 | expect(radial_area.inner_radius.(point)).to eq(50) 95 | expect(radial_area.outer_radius.(point)).to eq(117) 96 | expect(radial_area.radius.(point)).to eq(50) 97 | end 98 | 99 | it ".line_start_angle" do # start_angle inner_radius 100 | expect(radial_area.line_start_angle).to be_instance_of(D3::RadialLineGenerator) 101 | expect(radial_area.line_start_angle.angle.(point)).to eq(2.1) 102 | expect(radial_area.line_start_angle.radius.(point)).to eq(50) 103 | end 104 | 105 | it ".line_end_angle" do # end_angle inner_radius 106 | expect(radial_area.line_end_angle).to be_instance_of(D3::RadialLineGenerator) 107 | expect(radial_area.line_end_angle.angle.(point)).to eq(4.2) 108 | expect(radial_area.line_end_angle.radius.(point)).to eq(50) 109 | end 110 | 111 | it ".line_inner_radius" do # start_angle inner_radius 112 | expect(radial_area.line_inner_radius).to be_instance_of(D3::RadialLineGenerator) 113 | expect(radial_area.line_inner_radius.angle.(point)).to eq(2.1) 114 | expect(radial_area.line_inner_radius.radius.(point)).to eq(50) 115 | end 116 | 117 | it ".line_outer_radius" do # start_angle outer_radius 118 | expect(radial_area.line_outer_radius).to be_instance_of(D3::RadialLineGenerator) 119 | expect(radial_area.line_outer_radius.angle.(point)).to eq(2.1) 120 | expect(radial_area.line_outer_radius.radius.(point)).to eq(117) 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /spec/radial_line_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - radial line" do 2 | it "d3.radial_line" do 3 | expect(D3.radial_line).to be_instance_of(D3::RadialLineGenerator) 4 | end 5 | 6 | let(:simple_data) { [[0,2],[Math::PI*0.10,5],[Math::PI*0.20,3]] } 7 | 8 | it "basics" do 9 | expect(D3.radial_line.(simple_data)).to eq("M0,-2L1.545084971874737,-4.755282581475767L1.7633557568774194,-2.4270509831248424") 10 | end 11 | 12 | it "curve" do 13 | expect(D3.radial_line.curve).to be_instance_of(D3::Curve) 14 | expect(D3.radial_line.curve(D3.curve_natural).(simple_data)).to eq(%W[ 15 | M0,-2 16 | C0.6255961728642502,-3.3420537088108135,1.2511923457285004,-4.684107417621627,1.545084971874737,-4.755282581475767 17 | C1.8389775980209737,-4.826457745329908,1.8011666774491966,-3.626754364227375,1.7633557568774194,-2.4270509831248424 18 | ].join) 19 | end 20 | 21 | it "angle/radius accessors" do 22 | radial_line = D3.radial_line 23 | radial_line.angle{|(a,r)| a*2}.radius{|(a,r)| r*100} 24 | expect(radial_line.angle.([2,42])).to eq(4) 25 | expect(radial_line.radius.([2,42])).to eq(4200) 26 | expect(radial_line.(simple_data)).to eq("M0,-200L293.8926261462366,-404.5084971874737L285.31695488854604,-92.70509831248424") 27 | end 28 | 29 | it ".angle constant" do 30 | radial_line = D3.radial_line.angle(Math::PI) 31 | expect(radial_line.(simple_data)).to eq("M2.4492935982947064e-16,2L6.123233995736766e-16,5L3.6739403974420594e-16,3") 32 | expect(radial_line.angle.(42)).to eq(Math::PI) 33 | radial_line.angle = 20 34 | expect(radial_line.angle.(42)).to eq(20) 35 | end 36 | 37 | it ".radius constant" do 38 | radial_line = D3.radial_line.radius(10) 39 | expect(radial_line.(simple_data)).to eq("M0,-10L3.090169943749474,-9.510565162951535L5.877852522924732,-8.090169943749475") 40 | expect(radial_line.radius.(42)).to eq(10) 41 | radial_line.radius = 20 42 | expect(radial_line.radius.(42)).to eq(20) 43 | end 44 | 45 | it ".defined" do 46 | radial_line = D3.radial_line.defined{|(a,r)| r.even?} 47 | data = [[0,6], [0,10], [0,11], [0,14], [0,16], [0,19]] 48 | expect(radial_line.(data)).to eq("M0,-6L0,-10M0,-14L0,-16") 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/random_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-random" do 2 | # Not really testing anything more meaningful, 3 | # just that they return some numbers 4 | # http://xkcd.com/221/ 5 | it "d3.random_uniform" do 6 | expect(D3.random_uniform.call).to be_a(Numeric) 7 | expect(D3.random_uniform(10).call).to be_a(Numeric) 8 | expect(D3.random_uniform(10,20).call).to be_a(Numeric) 9 | end 10 | 11 | it "d3.random_normal" do 12 | expect(D3.random_normal.call).to be_a(Numeric) 13 | expect(D3.random_normal(10).call).to be_a(Numeric) 14 | expect(D3.random_normal(10, 2).call).to be_a(Numeric) 15 | end 16 | 17 | it "d3.random_log_normal" do 18 | expect(D3.random_log_normal.call).to be_a(Numeric) 19 | expect(D3.random_log_normal(10).call).to be_a(Numeric) 20 | expect(D3.random_log_normal(10, 2).call).to be_a(Numeric) 21 | end 22 | 23 | it "d3.random_bates" do 24 | expect(D3.random_bates(5).call).to be_a(Numeric) 25 | end 26 | 27 | it "d3.random_irwin_hall" do 28 | expect(D3.random_irwin_hall(5).call).to be_a(Numeric) 29 | end 30 | 31 | it "d3.random_exponential" do 32 | expect(D3.random_exponential(3).call).to be_a(Numeric) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/request_spec.rb: -------------------------------------------------------------------------------- 1 | # opal-rspec and async testing don't currently work very well 2 | # so these are completely untested, except for the doesn't crash part 3 | describe "d3-random" do 4 | it "d3.json" do 5 | D3.json("/test.json") 6 | end 7 | 8 | it "d3.json" do 9 | D3.json("/test.json") 10 | end 11 | 12 | it "d3.json" do 13 | D3.json("/test.json") 14 | end 15 | 16 | it "d3.text" do 17 | D3.text("/test.text") 18 | end 19 | 20 | it "d3.tsv" do 21 | D3.tsv("/test.tsv") 22 | end 23 | 24 | it "d3.xml" do 25 | D3.xml("/test.xml") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/search_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-array - search" do 2 | it "d3.ascending" do 3 | expect(D3.ascending(2,1)).to eq(1) 4 | expect(D3.ascending(1,1)).to eq(0) 5 | expect(D3.ascending(1,2)).to eq(-1) 6 | end 7 | 8 | it "d3.descending" do 9 | expect(D3.descending(2,1)).to eq(-1) 10 | expect(D3.descending(1,1)).to eq(0) 11 | expect(D3.descending(1,2)).to eq(1) 12 | end 13 | 14 | it "d3.scan" do 15 | array = [13,5,11,26,32] 16 | expect(D3.scan(array)).to eq(1) 17 | expect(D3.scan(array){|a,b| (a%10) - (b%10)}).to eq(2) 18 | expect(D3.scan(array){|a,b| (b%10) - (a%10)}).to eq(3) 19 | end 20 | 21 | it "d3.bisect_left" do 22 | array = (100..200).to_a 23 | expect(D3.bisect_left(array, 117)).to eq(17) 24 | expect(D3.bisect_left(array, 130.5)).to eq(31) 25 | end 26 | 27 | it "d3.bisect_right" do 28 | array = (100..200).to_a 29 | expect(D3.bisect_right(array, 117)).to eq(18) 30 | expect(D3.bisect_right(array, 130.5)).to eq(31) 31 | end 32 | 33 | it "d3.bisect" do 34 | array = (100..200).to_a 35 | expect(D3.bisect(array, 117)).to eq(18) 36 | expect(D3.bisect(array, 130.5)).to eq(31) 37 | end 38 | 39 | it "d3.bisector" do 40 | expect(D3.bisector{|x| x.abs}).to be_instance_of(D3::Bisector) 41 | expect(D3.bisector{|x,y| x.abs - y.abs}).to be_instance_of(D3::Bisector) 42 | end 43 | 44 | describe "D3::Bisector" do 45 | let(:array) { (100..200).map{|i| i * (-1)**i} } 46 | let(:a) { D3.bisector{|x| x.abs} } 47 | let(:c) { D3.bisector{|x,y| x.abs - y.abs} } 48 | 49 | it "A.left" do 50 | expect(a.left(array, 117)).to eq(17) 51 | expect(a.left(array, 130.5)).to eq(31) 52 | end 53 | 54 | it "C.left" do 55 | expect(c.left(array, 117)).to eq(17) 56 | expect(c.left(array, 130.5)).to eq(31) 57 | end 58 | 59 | it "A.right" do 60 | expect(a.right(array, 117)).to eq(18) 61 | expect(a.right(array, 130.5)).to eq(31) 62 | end 63 | 64 | it "C.right" do 65 | expect(c.right(array, 117)).to eq(18) 66 | expect(c.right(array, 130.5)).to eq(31) 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/selection_data_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - selection - data" do 2 | after(:each) do 3 | D3.select("#test-area").html("") 4 | end 5 | let(:root) { D3.select("#test-area") } 6 | let(:html) { root.html } 7 | 8 | describe "list" do 9 | let(:data) {[ 10 | {name: "A", value: 10}, 11 | {name: "B", value: 20}, 12 | {name: "C", value: 30}, 13 | ]} 14 | it "enter" do 15 | root 16 | .append("ul") 17 | .select_all("li") 18 | .data(data) 19 | .enter 20 | .append("li") 21 | .html{|d| "#{d[:name]}"} 22 | .style("font-size"){|d| "#{d[:value]}px"} 23 | expect(html).to eq([ 24 | %Q[
    ], 25 | %Q[
  • A
  • ], 26 | %Q[
  • B
  • ], 27 | %Q[
  • C
  • ], 28 | %Q[
], 29 | ].join) 30 | end 31 | end 32 | 33 | describe "matrix" do 34 | let(:data) { 35 | [ 36 | [11975, 5871, 8916, 2868], 37 | [ 1951, 10048, 2060, 6171], 38 | [ 8010, 16145, 8090, 8045], 39 | [ 1013, 990, 940, 6907], 40 | ] 41 | } 42 | it do 43 | tr = root 44 | .append("table") 45 | .select_all("tr") 46 | .data(data) 47 | .enter 48 | .append("tr") 49 | tr.select_all("td") 50 | .data{|d| d} 51 | .enter 52 | .append("td") 53 | .text{|d| d} 54 | expect(html).to eq([ 55 | %Q[], 56 | %Q[], 57 | %Q[], 58 | %Q[], 59 | %Q[], 60 | %Q[
11975587189162868
19511004820606171
80101614580908045
10139909406907
], 61 | ].join) 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/selection_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - selection" do 2 | after(:each) do 3 | D3.select("#test-area").html("") 4 | end 5 | 6 | it "d3.selection" do 7 | s = D3.selection 8 | expect(s).to be_instance_of(D3::Selection) 9 | expect(s.size).to eq(1) 10 | end 11 | 12 | it "d3.select" do 13 | s = D3.select("div") 14 | expect(s).to be_instance_of(D3::Selection) 15 | expect(s.size).to eq(1) 16 | expect(s.empty?).to eq(false) 17 | 18 | s = D3.select("h6") 19 | expect(s).to be_instance_of(D3::Selection) 20 | expect(s.size).to eq(0) 21 | expect(s.empty?).to eq(true) 22 | end 23 | 24 | it "d3.select_all" do 25 | s = D3.select_all("div") 26 | expect(s).to be_instance_of(D3::Selection) 27 | expect(s.size).to eq(1) 28 | expect(s.empty?).to eq(false) 29 | 30 | s = D3.select_all("h6") 31 | expect(s).to be_instance_of(D3::Selection) 32 | expect(s.size).to eq(0) 33 | expect(s.empty?).to eq(true) 34 | end 35 | 36 | describe "nested selections" do 37 | before(:each) do 38 | D3.select("div").html(" 39 |

123

40 |

456

41 | ") 42 | end 43 | 44 | it "selection.select" do 45 | expect(D3.select("p").select("b").size).to eq(1) 46 | expect(D3.select_all("p").select("b").size).to eq(2) 47 | end 48 | 49 | it "selection.select_all" do 50 | expect(D3.select("p").select_all("b").size).to eq(3) 51 | expect(D3.select_all("p").select_all("b").size).to eq(6) 52 | end 53 | end 54 | 55 | describe "selection.filter - selector string" do 56 | before(:each) do 57 | D3.select("div").html(" 58 | 1 59 | 2 60 | 3 61 | ") 62 | end 63 | let(:a) { D3.select_all("span.a") } 64 | let(:b) { D3.select_all("span.b") } 65 | let(:c) { D3.select_all("span.c") } 66 | let(:d) { D3.select_all("span.d") } 67 | 68 | it do 69 | expect(b.filter(".c").size).to eq(1) 70 | end 71 | end 72 | 73 | describe "selection.filter - filter" do 74 | before(:each) do 75 | D3.select("div") 76 | .select_all("span") 77 | .data(%W[a b c d]) 78 | .enter 79 | .append("span") 80 | .text{|d| d} 81 | end 82 | 83 | it "function" do 84 | D3.select_all("span").filter{|d| d =~ /[bc]/}.attr("class", "x") 85 | expect(D3.select("div").html).to eq( 86 | %Q[abcd] 87 | ) 88 | end 89 | 90 | it "function with index" do 91 | D3.select_all("span").filter{|d,i| i.even?}.attr("class", "y") 92 | expect(D3.select("div").html).to eq( 93 | %Q[abcd] 94 | ) 95 | end 96 | end 97 | 98 | describe "selection.each" do 99 | before(:each) do 100 | D3.select("div") 101 | .select_all("span") 102 | .data(%W[a b c d]) 103 | .enter 104 | .append("span") 105 | .text{|d| d} 106 | end 107 | it do 108 | results = [] 109 | D3.select_all("span").each do |n| 110 | results << n 111 | end 112 | expect(results).to eq(["a", "b", "c", "d"]) 113 | end 114 | 115 | it do 116 | results = [] 117 | D3.select_all("span").each do |n,i| 118 | results << [n,i] 119 | end 120 | expect(results).to eq([["a", 0], ["b", 1], ["c", 2], ["d", 3]]) 121 | end 122 | end 123 | 124 | it "selection.append" do 125 | div = D3.select_all("div") 126 | ul = div.append("ul") 127 | ul.append("li") 128 | ul.append("li") 129 | expect(div.html).to eq("
") 130 | ul.select_all("li").text = "WOW" 131 | expect(div.html).to eq("
  • WOW
  • WOW
") 132 | end 133 | 134 | it "selection.html / selection.text" do 135 | div = D3.select_all("div") 136 | expect(div.html).to eq("") 137 | expect(div.text).to eq("") 138 | 139 | div.html = "

Hello, World!

" 140 | expect(div.html).to eq("

Hello, World!

") 141 | expect(div.text).to eq("Hello, World!") 142 | 143 | h1 = div.select("h1") 144 | expect(h1.text).to eq("Hello, World!") 145 | h1.text = "Goodbye, World!" 146 | expect(div.html).to eq("

Goodbye, World!

") 147 | expect(div.text).to eq("Goodbye, World!") 148 | expect(h1.html).to eq("Goodbye, World!") 149 | expect(h1.text).to eq("Goodbye, World!") 150 | end 151 | 152 | it "svg" do 153 | D3.select("div") 154 | .append("svg") 155 | .attr("width", 960) 156 | .attr("height", 500) 157 | .append("g") 158 | .attr("transform", "translate(20,20)") 159 | .append("rect") 160 | .attr("width", 920) 161 | .attr("height", 460) 162 | expect(D3.select("div").html).to eq( 163 | %Q[]) 164 | end 165 | 166 | describe do 167 | before(:each) do 168 | D3.select("div").html(" 169 |

123

170 |

456

171 | ") 172 | end 173 | 174 | # These should use opal-browser, but that seems to be broken with phantomjs 175 | # For now just expose raw js objects 176 | it "selection.node" do 177 | expect(D3.select_all("b").node).to be_instance_of(Native::Object) 178 | end 179 | 180 | it "selection.nodes" do 181 | expect(D3.select_all("b").nodes).to be_instance_of(Array) 182 | D3.select_all("b").nodes.each do |n| 183 | expect(n).to be_instance_of(Native::Object) 184 | end 185 | end 186 | end 187 | end 188 | -------------------------------------------------------------------------------- /spec/sequential_scale_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - sequential scale" do 2 | let(:scale) { D3.scale_sequential(&D3.send(interpolator)).domain([0, 100]) } 3 | let(:curve_direct) { (0..20).map{|t| D3.send(interpolator, t/20.0)} } 4 | let(:curve_scale) { (0..20).map{|t| scale.(t*100/20.0)} } 5 | 6 | it "d3.scale_sequential" do 7 | expect(D3.scale_sequential(&D3.interpolate_viridis)).to be_instance_of(D3::SequentialScale) 8 | expect(D3.scale_sequential{|t| D3.hsl(t * 360, 1, 0.5) }).to be_instance_of(D3::SequentialScale) 9 | end 10 | 11 | it "basics" do 12 | s = D3.scale_sequential{|t| D3.hsl(t * 360, 1, 0.5).to_s } 13 | expect(s.domain).to eq([0,1]) 14 | s2 = s.copy.domain([0,100]).clamp(true) 15 | expect(s.domain).to eq([0,1]) 16 | expect(s2.domain).to eq([0,100]) 17 | expect(s.clamp).to eq(false) 18 | expect(s2.clamp).to eq(true) 19 | expect(s.(0.2)).to eq("rgb(204, 255, 0)") 20 | expect(s.(1.2)).to eq("rgb(204, 255, 0)") 21 | expect(s2.(20)).to eq("rgb(204, 255, 0)") 22 | expect(s2.(120)).to eq("rgb(255, 0, 0)") 23 | expect(s.interpolator.(0.3)).to eq("rgb(51, 255, 0)") 24 | expect(s2.interpolator.(0.3)).to eq("rgb(51, 255, 0)") 25 | end 26 | 27 | describe "viridis" do 28 | let(:interpolator) { :interpolate_viridis } 29 | it do 30 | expect(curve_direct).to eq(curve_scale) 31 | expect(curve_scale).to eq(["#440154", "#471365", "#482475", "#463480", "#414487", "#3b528b", "#355f8d", "#2f6c8e", "#2a788e", "#25848e", "#21918c", "#1e9c89", "#22a884", "#2fb47c", "#44bf70", "#5ec962", "#7ad151", "#9bd93c", "#bddf26", "#dfe318", "#fde725"]) 32 | end 33 | end 34 | 35 | describe "inferno" do 36 | let(:interpolator) { :interpolate_inferno } 37 | it do 38 | expect(curve_direct).to eq(curve_scale) 39 | expect(curve_scale).to eq(["#000004", "#07051b", "#160b39", "#2b0b57", "#420a68", "#57106e", "#6a176e", "#7f1e6c", "#932667", "#a82e5f", "#bc3754", "#cc4248", "#dd513a", "#ea632a", "#f37819", "#f98e09", "#fca50a", "#fbbe23", "#f6d746", "#f1ef75", "#fcffa4"]) 40 | end 41 | end 42 | 43 | describe "magma" do 44 | let(:interpolator) { :interpolate_magma } 45 | it do 46 | expect(curve_direct).to eq(curve_scale) 47 | expect(curve_scale).to eq(["#000004", "#06051a", "#140e36", "#251255", "#3b0f70", "#51127c", "#641a80", "#782281", "#8c2981", "#a1307e", "#b73779", "#ca3e72", "#de4968", "#ed5a5f", "#f7705c", "#fc8961", "#fe9f6d", "#feb77e", "#fecf92", "#fde7a9", "#fcfdbf"]) 48 | end 49 | end 50 | 51 | describe "plasma" do 52 | let(:interpolator) { :interpolate_plasma } 53 | it do 54 | expect(curve_direct).to eq(curve_scale) 55 | expect(curve_scale).to eq(["#0d0887", "#2a0593", "#41049d", "#5601a4", "#6a00a8", "#7e03a8", "#8f0da4", "#a11b9b", "#b12a90", "#bf3984", "#cc4778", "#d6556d", "#e16462", "#ea7457", "#f2844b", "#f89540", "#fca636", "#feba2c", "#fcce25", "#f7e425", "#f0f921"]) 56 | end 57 | end 58 | 59 | describe "warm" do 60 | let(:interpolator) { :interpolate_warm } 61 | it do 62 | expect(curve_direct).to eq(curve_scale) 63 | expect(curve_scale).to eq(["rgb(110, 64, 170)", "rgb(129, 62, 176)", "rgb(150, 61, 179)", "rgb(171, 60, 178)", "rgb(191, 60, 175)", "rgb(210, 62, 167)", "rgb(228, 65, 157)", "rgb(242, 69, 145)", "rgb(254, 75, 131)", "rgb(255, 84, 115)", "rgb(255, 94, 99)", "rgb(255, 106, 84)", "rgb(255, 120, 71)", "rgb(255, 135, 59)", "rgb(251, 150, 51)", "rgb(239, 167, 47)", "rgb(226, 183, 47)", "rgb(212, 199, 51)", "rgb(198, 214, 60)", "rgb(186, 228, 73)", "rgb(175, 240, 91)"]) 64 | end 65 | end 66 | 67 | describe "cool" do 68 | let(:interpolator) { :interpolate_cool } 69 | it do 70 | expect(curve_direct).to eq(curve_scale) 71 | expect(curve_scale).to eq(["rgb(110, 64, 170)", "rgb(104, 73, 186)", "rgb(96, 84, 200)", "rgb(87, 97, 211)", "rgb(76, 110, 219)", "rgb(65, 125, 224)", "rgb(54, 140, 225)", "rgb(44, 156, 223)", "rgb(35, 171, 216)", "rgb(29, 186, 206)", "rgb(26, 199, 194)", "rgb(26, 212, 179)", "rgb(29, 223, 163)", "rgb(37, 232, 146)", "rgb(48, 239, 130)", "rgb(64, 243, 115)", "rgb(82, 246, 103)", "rgb(103, 247, 94)", "rgb(127, 246, 88)", "rgb(151, 243, 87)", "rgb(175, 240, 91)"]) 72 | end 73 | end 74 | 75 | describe "rainbow" do 76 | let(:interpolator) { :interpolate_rainbow } 77 | it do 78 | expect(curve_direct).to eq(curve_scale) 79 | expect(curve_scale).to eq(["rgb(110, 64, 170)", "rgb(150, 61, 179)", "rgb(191, 60, 175)", "rgb(228, 65, 157)", "rgb(254, 75, 131)", "rgb(255, 94, 99)", "rgb(255, 120, 71)", "rgb(251, 150, 51)", "rgb(226, 183, 47)", "rgb(198, 214, 60)", "rgb(175, 240, 91)", "rgb(127, 246, 88)", "rgb(82, 246, 103)", "rgb(48, 239, 130)", "rgb(29, 223, 163)", "rgb(26, 199, 194)", "rgb(35, 171, 216)", "rgb(54, 140, 225)", "rgb(76, 110, 219)", "rgb(96, 84, 200)", "rgb(110, 64, 170)"]) 80 | end 81 | end 82 | 83 | describe "cubehelix_default" do 84 | let(:interpolator) { :interpolate_cubehelix_default } 85 | it do 86 | expect(curve_direct).to eq(curve_scale) 87 | expect(curve_scale).to eq(["rgb(0, 0, 0)", "rgb(18, 8, 23)", "rgb(26, 21, 48)", "rgb(26, 39, 68)", "rgb(22, 61, 78)", "rgb(22, 83, 76)", "rgb(31, 102, 66)", "rgb(52, 115, 53)", "rgb(84, 121, 47)", "rgb(122, 122, 53)", "rgb(160, 121, 73)", "rgb(190, 121, 106)", "rgb(208, 126, 147)", "rgb(212, 138, 186)", "rgb(207, 156, 218)", "rgb(199, 179, 237)", "rgb(193, 202, 243)", "rgb(197, 222, 242)", "rgb(210, 238, 239)", "rgb(232, 248, 242)", "rgb(255, 255, 255)"]) 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/set_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-collections - sets" do 2 | it "d3.set" do 3 | expect(D3.set).to be_instance_of(D3::Set) 4 | expect(D3.set.values).to eq([]) 5 | expect(D3.set([1,-2,3,1]).values).to eq(["1","-2","3"]) 6 | expect(D3.set([1,-2,3,-1], &:abs).values).to eq(["1","2","3"]) 7 | end 8 | 9 | it "set.has?" do 10 | expect(D3.set(["1"]).has?("2")).to eq(false) 11 | # everything converted to strings 12 | expect(D3.set(["1"]).has?("1")).to eq(true) 13 | expect(D3.set([1]).has?(1)).to eq(true) 14 | expect(D3.set([1]).has?("1")).to eq(true) 15 | expect(D3.set(["1"]).has?(1)).to eq(true) 16 | end 17 | 18 | it "set.add" do 19 | s = D3.set() 20 | s.add(1).add(2).add(3).add(1) 21 | expect(s.values).to eq(["1", "2", "3"]) 22 | end 23 | 24 | it "set.clear" do 25 | s = D3.set() 26 | s.add(1).add(2).add(3).clear().add(1) 27 | expect(s.values).to eq(["1"]) 28 | end 29 | 30 | it "set.values" do 31 | expect(D3.set().values).to eq([]) 32 | expect(D3.set([1]).values).to eq(["1"]) 33 | expect(D3.set([1,2,"1","2",3]).values).to eq(["1","2","3"]) 34 | end 35 | 36 | it "set.each" do 37 | called = [] 38 | D3.set([1,2,3]).each{|x| 39 | called << x 40 | } 41 | expect(called).to eq(["1","2","3"]) 42 | end 43 | 44 | it "set.empty?" do 45 | a = D3.set() 46 | expect(a).to be_empty 47 | b = D3.set([1]) 48 | expect(b).to_not be_empty 49 | end 50 | 51 | it "set.size" do 52 | a = D3.set() 53 | expect(a.size).to eq(0) 54 | b = D3.set([1,2,3]) 55 | expect(b.size).to eq(3) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "opal-rspec" 2 | require "opal-d3" 3 | -------------------------------------------------------------------------------- /spec/stack_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - stack" do 2 | it "d3.stack" do 3 | expect(D3.stack).to be_instance_of(D3::StackGenerator) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/statistics_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-array - statistics" do 2 | it "d3.min" do 3 | expect(D3.min([2,1,3])).to eq(1) 4 | expect(D3.max([2,-1,-3,0], &:-@)).to eq(3) 5 | expect(D3.min([2,1,nil,3])).to eq(1) 6 | expect(D3.min(["20","3"])).to eq("20") 7 | expect(D3.min([20,3])).to eq(3) 8 | end 9 | 10 | it "d3.max" do 11 | expect(D3.max([2,1,3,0])).to eq(3) 12 | expect(D3.max([2,-1,-3,0], &:abs)).to eq(3) 13 | end 14 | 15 | it "d3.extent" do 16 | expect(D3.extent([2,-1,3])).to eq([-1,3]) 17 | expect(D3.extent([2,-1,nil,3])).to eq([-1,3]) 18 | expect(D3.extent([2,-1,3], &:abs)).to eq([1,3]) 19 | expect(D3.extent([1])).to eq([1,1]) 20 | expect(D3.extent([])).to eq([nil,nil]) 21 | end 22 | 23 | it "d3.sum" do 24 | expect(D3.sum([1,2,3])).to eq(6) 25 | expect(D3.sum([1,2,3,nil])).to eq(6) 26 | expect(D3.sum([1,2,3]){|x| x**2}).to eq(1+4+9) 27 | expect(D3.sum([])).to eq(0) 28 | end 29 | 30 | it "d3.mean" do 31 | expect(D3.mean([1,2,3])).to eq(2) 32 | expect(D3.mean([1,2,3,nil])).to eq(2) 33 | expect(D3.mean([1,2,3]){|x| x**2}).to eq((1+4+9)/3) 34 | expect(D3.mean([1,2,3,4]){|x| x > 3 ? nil : x**2}).to eq((1+4+9)/3) 35 | expect(D3.mean([])).to eq(nil) 36 | end 37 | 38 | it "d3.median" do 39 | expect(D3.median([1,2,3])).to eq(2) 40 | expect(D3.median([1,2,3,nil,nil])).to eq(2) 41 | expect(D3.median([])).to eq(nil) 42 | end 43 | 44 | it "d3.quantile" do 45 | # Examples from d3 documentation 46 | expect(D3.quantile([0, 10, 30], 0)).to eq(0) 47 | expect(D3.quantile([0, 10, 30], 0.5)).to eq(10) 48 | expect(D3.quantile([0, 10, 30], 1)).to eq(30) 49 | expect(D3.quantile([0, 10, 30], 0.25)).to eq(5) 50 | expect(D3.quantile([0, 10, 30], 0.75)).to eq(20) 51 | expect(D3.quantile([0, 10, 30], 0.1)).to eq(2) 52 | end 53 | 54 | it "d3.variance" do 55 | expect(D3.variance([])).to eq(nil) 56 | expect(D3.variance([1])).to eq(nil) 57 | expect(D3.variance([10,20,30])).to eq(100) 58 | end 59 | 60 | it "d3.deviation" do 61 | expect(D3.deviation([])).to eq(nil) 62 | expect(D3.deviation([1])).to eq(nil) 63 | expect(D3.deviation([10,20,30])).to eq(10) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/symbol_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - symbol" do 2 | let(:path) { symbol.() } 3 | let(:rounded) { path.gsub(/\d+\.\d+/){$&.to_f.round(2)} } 4 | 5 | it "d3.symbol" do 6 | expect(D3.symbol).to be_instance_of(D3::SymbolGenerator) 7 | end 8 | 9 | describe "basics" do 10 | let(:symbol) { D3.symbol } 11 | it do 12 | expect(rounded).to eq("M4.51,0A4.51,4.51,0,1,1,-4.51,0A4.51,4.51,0,1,1,4.51,0") 13 | end 14 | end 15 | 16 | describe "static size/type" do 17 | let(:symbol) { D3.symbol.type(D3.symbol_triangle).size(1000) } 18 | it do 19 | expect(rounded).to eq("M0,-27.75L24.03,13.87L-24.03,13.87Z") 20 | end 21 | end 22 | 23 | describe "fuctional size/type" do 24 | let(:symbol) { 25 | D3.symbol 26 | .size{|d| d[:size]} 27 | .type{|d| D3.symbols[d[:type]]} 28 | } 29 | let(:path) { symbol.(data) } 30 | describe do 31 | let(:data) {{ size: 100, type: 0 }} 32 | it do 33 | expect(rounded).to eq("M5.64,0A5.64,5.64,0,1,1,-5.64,0A5.64,5.64,0,1,1,5.64,0") 34 | end 35 | end 36 | describe do 37 | let(:data) {{ size: 200, type: 0 }} 38 | it do 39 | expect(rounded).to eq("M7.98,0A7.98,7.98,0,1,1,-7.98,0A7.98,7.98,0,1,1,7.98,0") 40 | end 41 | end 42 | describe do 43 | let(:data) {{ size: 100, type: 1 }} 44 | it do 45 | expect(rounded).to eq( "M-6.71,-2.24L-2.24,-2.24L-2.24,-6.71L2.24,-6.71L2.24,-2.24L6.71,-2.24L6.71,2.24L2.24,2.24L2.24,6.71L-2.24,6.71L-2.24,2.24L-6.71,2.24Z") 46 | end 47 | end 48 | end 49 | 50 | it "d3.symbols" do 51 | expect(D3.symbols.size).to eq(7) 52 | D3.symbols.each do |sym| 53 | expect(sym).to be_instance_of(D3::SymbolType) 54 | end 55 | end 56 | 57 | describe "symbol types" do 58 | let(:symbol) { D3.symbol.type(symbol_type) } 59 | 60 | describe "d3.symbol_circle" do 61 | let(:symbol_type) { D3.symbol_circle } 62 | it do 63 | expect(symbol_type).to be_instance_of(D3::SymbolType) 64 | expect(rounded).to eq("M4.51,0A4.51,4.51,0,1,1,-4.51,0A4.51,4.51,0,1,1,4.51,0") 65 | end 66 | end 67 | 68 | describe "d3.symbol_cross" do 69 | let(:symbol_type) { D3.symbol_cross } 70 | it do 71 | expect(symbol_type).to be_instance_of(D3::SymbolType) 72 | expect(rounded).to eq("M-5.37,-1.79L-1.79,-1.79L-1.79,-5.37L1.79,-5.37L1.79,-1.79L5.37,-1.79L5.37,1.79L1.79,1.79L1.79,5.37L-1.79,5.37L-1.79,1.79L-5.37,1.79Z") 73 | end 74 | end 75 | 76 | describe "d3.symbol_diamond" do 77 | let(:symbol_type) { D3.symbol_diamond } 78 | it do 79 | expect(symbol_type).to be_instance_of(D3::SymbolType) 80 | expect(rounded).to eq("M0,-7.44L4.3,0L0,7.44L-4.3,0Z") 81 | end 82 | end 83 | 84 | describe "d3.symbol_square" do 85 | let(:symbol_type) { D3.symbol_square } 86 | it do 87 | expect(symbol_type).to be_instance_of(D3::SymbolType) 88 | expect(rounded).to eq("M-4,-4h8v8h-8Z") 89 | end 90 | end 91 | 92 | describe "d3.symbol_star" do 93 | let(:symbol_type) { D3.symbol_star } 94 | it do 95 | expect(symbol_type).to be_instance_of(D3::SymbolType) 96 | expect(rounded).to eq("M0,-7.55L1.7,-2.33L7.18,-2.33L2.74,0.89L4.44,6.11L4.44e-16,2.88L-4.44,6.11L-2.74,0.89L-7.18,-2.33L-1.7,-2.33Z") 97 | end 98 | end 99 | 100 | describe "d3.symbol_triangle" do 101 | let(:symbol_type) { D3.symbol_triangle } 102 | it do 103 | expect(symbol_type).to be_instance_of(D3::SymbolType) 104 | expect(rounded).to eq("M0,-7.02L6.08,3.51L-6.08,3.51Z") 105 | end 106 | end 107 | 108 | describe "d3.symbol_wye" do 109 | let(:symbol_type) { D3.symbol_wye } 110 | it do 111 | expect(symbol_type).to be_instance_of(D3::SymbolType) 112 | expect(rounded).to eq("M2.16,1.25L2.16,5.56L-2.16,5.56L-2.16,1.25L-5.9,-0.91L-3.74,-4.65L0,-2.49L3.74,-4.65L5.9,-0.91Z") 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /spec/threshold_scale_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3 - threshold scale" do 2 | let(:color) { D3.scale_threshold.domain([0, 1]).range(["red", "white", "green"]) } 3 | it "d3.scale_threshold" do 4 | expect(D3.scale_threshold).to be_instance_of(D3::ThresholdScale) 5 | end 6 | 7 | it "basics" do 8 | expect(color.(-1)).to eq("red") 9 | expect(color.(0)).to eq("white") 10 | expect(color.(0.5)).to eq("white") 11 | expect(color.(1)).to eq("green") 12 | expect(color.(1000)).to eq("green") 13 | end 14 | 15 | it "invert_extent" do 16 | expect(color.invert_extent("red")).to eq([nil, 0]) 17 | expect(color.invert_extent("white")).to eq([0, 1]) 18 | expect(color.invert_extent("green")).to eq([1, nil]) 19 | end 20 | 21 | it "copy" do 22 | cc = color.copy.domain([0,10]) 23 | expect(cc.domain).to eq([0,10]) 24 | expect(cc.range).to eq(["red", "white", "green"]) 25 | expect(color.domain).to eq([0,1]) 26 | expect(color.range).to eq(["red", "white", "green"]) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/time_format_spec.rb: -------------------------------------------------------------------------------- 1 | # This test will fail in any non-UTC timezone 2 | require "time" 3 | 4 | # Running these specs in weird timezone will fail because javascript timezones are pile of fail 5 | describe "d3 - time format" do 6 | let(:us) {{ 7 | date_time: "%x, %X", 8 | date: "%-m/%-d/%Y", 9 | time: "%-I:%M:%S %p", 10 | periods: ["AM", "PM"], 11 | days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], 12 | short_days: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], 13 | months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], 14 | short_months: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 15 | }} 16 | let(:pl) {{ 17 | date_time: "%A, %e %B %Y, %X", 18 | date: "%d/%m/%Y", 19 | time: "%H:%M:%S", 20 | periods: ["AM", "PM"], 21 | days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota"], 22 | short_days: ["Niedz.", "Pon.", "Wt.", "Śr.", "Czw.", "Pt.", "Sob."], 23 | months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"], 24 | short_months: ["Stycz.", "Luty", "Marz.", "Kwie.", "Maj", "Czerw.", "Lipc.", "Sierp.", "Wrz.", "Paźdz.", "Listop.", "Grudz."], 25 | }} 26 | 27 | it "d3.time_format" do 28 | f = D3.time_format("%B %d, %Y") 29 | expect(f).to be_instance_of(Proc) 30 | expect(f.(Time.parse("June 30, 2015"))).to eq("June 30, 2015") 31 | end 32 | 33 | it "d3.time_parse" do 34 | f = D3.time_parse("%B %d, %Y") 35 | expect(f).to be_instance_of(Proc) 36 | expect(f.("June 30, 2015")).to eq(Time.parse("June 30, 2015 00:00:00")) 37 | end 38 | 39 | it "d3.utc_format" do 40 | f = D3.utc_format("%B %d, %Y") 41 | expect(f).to be_instance_of(Proc) 42 | expect(f.(Time.parse("June 30, 2015 12:00:00"))).to eq("June 30, 2015") 43 | end 44 | 45 | it "d3.utc_parse" do 46 | f = D3.utc_parse("%B %d, %Y") 47 | expect(f).to be_instance_of(Proc) 48 | expect(f.("June 30, 2015")).to eq(Time.parse("June 30, 2015 00:00:00 UTC")) 49 | end 50 | 51 | it "d3.iso_format / d3.iso_parse" do 52 | t = Time.parse("June 30, 2015 12:30:45 UTC") 53 | s = "2015-06-30T12:30:45.000Z" 54 | expect(D3.iso_format(t)).to eq(s) 55 | expect(D3.iso_parse(s)).to eq(t) 56 | end 57 | 58 | describe "locale" do 59 | let(:locale) { D3.time_format_locale(pl) } 60 | 61 | it "d3.format" do 62 | f = locale.format("%B %d, %Y") 63 | expect(f).to be_instance_of(Proc) 64 | expect(f.(Time.parse("June 30, 2015"))).to eq("Czerwiec 30, 2015") 65 | end 66 | 67 | it "d3.parse" do 68 | f = locale.parse("%B %d, %Y") 69 | expect(f).to be_instance_of(Proc) 70 | expect(f.("Czerwiec 30, 2015")).to eq(Time.parse("June 30, 2015 00:00:00")) 71 | end 72 | 73 | it "d3.utc_format" do 74 | f = locale.utc_format("%B %d, %Y") 75 | expect(f).to be_instance_of(Proc) 76 | expect(f.(Time.parse("June 30, 2015 12:00:00"))).to eq("Czerwiec 30, 2015") 77 | end 78 | 79 | it "d3.utc_parse" do 80 | f = locale.utc_parse("%B %d, %Y") 81 | expect(f).to be_instance_of(Proc) 82 | expect(f.("Czerwiec 30, 2015")).to eq(Time.parse("June 30, 2015 00:00:00 UTC")) 83 | end 84 | end 85 | 86 | # This test affects global state, so we must be sure to restore it 87 | it "d3.time_format_default_locale" do 88 | t = Time.parse("June 30, 2015 12:30:00") 89 | expect(D3.time_format("%c").(t)).to eq("6/30/2015, 12:30:00 PM") 90 | 91 | locale_pl = D3.time_format_default_locale(pl) 92 | expect(D3.time_format("%c").(t)).to eq("Wtorek, 30 Czerwiec 2015, 12:30:00") 93 | 94 | locale_us = D3.time_format_default_locale(us) 95 | expect(D3.time_format("%c").(t)).to eq("6/30/2015, 12:30:00 PM") 96 | 97 | expect(locale_pl.format("%c").(t)).to eq("Wtorek, 30 Czerwiec 2015, 12:30:00") 98 | expect(locale_us.format("%c").(t)).to eq("6/30/2015, 12:30:00 PM") 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /spec/transformations_spec.rb: -------------------------------------------------------------------------------- 1 | describe "d3-array - transformations" do 2 | it "d3.merge" do 3 | expect(D3.merge([[1], [2, 3]])).to eq([1, 2, 3]) 4 | end 5 | 6 | it "d3.pairs" do 7 | expect(D3.pairs([1, 2, 3, 4])).to eq([[1, 2], [2, 3], [3, 4]]) 8 | end 9 | 10 | it "d3.permute" do 11 | expect(D3.permute(["a", "b", "c"], [1, 2, 0])).to eq(["b", "c", "a"]) 12 | end 13 | 14 | it "d3.shuffle" do 15 | a = (1..20).to_a 16 | b = (1..20).to_a 17 | D3.shuffle(b) # modifies in place 18 | expect(a).not_to eq(b) # 61 bit chance of random test fail 19 | expect(a.sort).to eq(b.sort) 20 | end 21 | 22 | it "d3.ticks" do 23 | expect(D3.ticks(1, 47, 5)).to eq([10, 20, 30, 40]) 24 | end 25 | 26 | it "d3.tick_step" do 27 | expect(D3.tick_step(1, 47, 5)).to eq(10) 28 | end 29 | 30 | it "d3.range" do 31 | expect(D3.range(5)).to eq([0,1,2,3,4]) 32 | expect(D3.range(2, 7)).to eq([2,3,4,5,6]) 33 | expect(D3.range(10,50,5)).to eq([10,15,20,25,30,35,40,45]) 34 | expect(D3.range(0, 1, 0.2)).to eq([0, 0.2, 0.4, 0.6000000000000001, 0.8]) 35 | end 36 | 37 | it "d3.transpose" do 38 | expect(D3.transpose([[1,2],[3,4]])).to eq([[1,3],[2,4]]) 39 | end 40 | 41 | it "d3.zip" do 42 | expect(D3.zip([1, 2], [3, 4])).to eq([[1, 3], [2, 4]]) 43 | expect(D3.zip([1, 2, 5], [3, 4])).to eq([[1, 3], [2, 4]]) 44 | expect(D3.zip([1, 2], [3, 4, 5])).to eq([[1, 3], [2, 4]]) 45 | expect(D3.zip([1, 2], [3, 4], [5, 6])).to eq([[1, 3, 5], [2, 4, 6]]) 46 | expect(D3.zip([1, 2])).to eq([[1], [2]]) 47 | expect(D3.zip([1, 2], [])).to eq([]) 48 | expect(D3.zip([])).to eq([]) 49 | expect(D3.zip()).to eq([]) 50 | end 51 | end 52 | --------------------------------------------------------------------------------