├── .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 |
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[11975 | 5871 | 8916 | 2868 |
],
57 | %Q[1951 | 10048 | 2060 | 6171 |
],
58 | %Q[8010 | 16145 | 8090 | 8045 |
],
59 | %Q[1013 | 990 | 940 | 6907 |
],
60 | %Q[
],
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("")
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 |
--------------------------------------------------------------------------------