ginatra 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  rvm:
3
- - 1.8.7
3
+ - 1.9.2
4
4
  script: "bundle exec rake spec"
5
5
  before_script:
6
6
  - "bundle exec rake repo"
data/README.md CHANGED
@@ -9,34 +9,30 @@ repositories out of a set of specified directories using an array of glob-based
9
9
  paths. I have plans to make it function just as gitweb does, including leeching
10
10
  config files and suchlike.
11
11
 
12
- Updating to the Most Recent Release (Gem):
13
- ------------------------------------------
12
+ Installation
13
+ ------------
14
14
 
15
- - ` $ gem install ginatra`
16
- - (re)Move `~/.ginatra`
17
- - Run the following to open an irb with Ginatra loaded: ` $ irb -r 'rubygems' -r 'rubygems'`
18
- - In this irb session, run the following then close it: `>> Ginatra::Config.setup!`
15
+ **NEW: You should be using Ruby 1.9.2: because it's awesome!**
19
16
 
20
- **BEWARE**: The last method that you just called will dump a config to `~/.ginatra/config.yml`.
21
- This ignores anything already there, so be careful. You have been warned.
17
+ To install ginatra:
22
18
 
23
- You can now copy the contents your old `~/.ginatra/` file into `~/.ginatra/config.yml`.
19
+ $ gem install ginatra
20
+ ...
21
+ $ ginatra setup
22
+ checked deps
23
+ installed config
24
24
 
25
- Installation
26
- ------------
25
+ If you get those two lines of output, you're sorted. anything else and something
26
+ has gone wrong.
27
+
28
+ ### External Dependencies
27
29
 
28
30
  You should be using Git 1.6.3 or later just to be sure that it all works:
29
31
 
30
32
  $ git --version
31
33
  git version 1.6.3
32
34
 
33
- Next, just do the following (your setup may require sudo):
34
-
35
- $ gem install ginatra
36
-
37
- This pulls down most of the required dependencies too.
38
-
39
- The last dependency you need is pygments, an awesome python syntax highlighter.
35
+ The other dependency you need is pygments, an awesome python syntax highlighter.
40
36
  To check whether you have it, run:
41
37
 
42
38
  $ which pygmentize
@@ -92,6 +88,7 @@ Attribution
92
88
 
93
89
  - Samuel Elliott (lenary)
94
90
  - Ryan Bigg (radar)
91
+ - Jan Topiński (simcha)
95
92
 
96
93
  **Patches**
97
94
 
@@ -103,28 +100,29 @@ Attribution
103
100
 
104
101
  In a new spirit of openness, all those who submit a patch that gets applied will gain commit access to the main (lenary/ginatra) repository.
105
102
 
106
- **Thanks**
107
-
108
- Too many to name. Thanks be to you all.
109
-
110
103
  Screenshots
111
104
  -----------
112
105
 
113
106
  **Index**
114
107
 
115
- ![Ginatra Index](http://lenary-uploads.appspot.com/img/i?id=ag5sZW5hcnktdXBsb2Fkc3IMCxIFSW1hZ2UYox8M&w=500&h=500 "Ginatra Index")
108
+ ![Ginatra Index](http://cloud.github.com/downloads/lenary/ginatra/o%20\(5\).png "Ginatra Index")
116
109
 
117
110
  **Log**
118
111
 
119
- ![Ginatra Log](http://lenary-uploads.appspot.com/img/i?id=ag5sZW5hcnktdXBsb2Fkc3IMCxIFSW1hZ2UYvRcM&w=500&h=500 "Ginatra Log")
112
+ ![Ginatra Log](http://cloud.github.com/downloads/lenary/ginatra/o%20\(3\).png "Ginatra Log")
120
113
 
121
114
  **Commit**
122
115
 
123
- ![Ginatra Commit](http://lenary-uploads.appspot.com/img/i?id=ag5sZW5hcnktdXBsb2Fkc3IMCxIFSW1hZ2UYvBcM&w=500&h=500 "Ginatra Commit")
116
+ ![Ginatra Commit](http://cloud.github.com/downloads/lenary/ginatra/o%20\(4\).png "Ginatra Commit")
124
117
 
125
118
  **Tree**
126
119
 
127
- ![Ginatra Tree](http://lenary-uploads.appspot.com/img/i?id=ag5sZW5hcnktdXBsb2Fkc3IMCxIFSW1hZ2UYpB8M&w=500&h=500 "Ginatra Tree")
120
+ ![Ginatra Tree](http://cloud.github.com/downloads/lenary/ginatra/o%20\(2\).png "Ginatra Tree")
121
+
122
+ **Branch graph**
123
+
124
+ ![Branch graph](http://cloud.github.com/downloads/simcha/ginatra/branch-graph.png "Branch Graph")
125
+
128
126
 
129
127
  Licence
130
128
  -------
data/Rakefile CHANGED
@@ -1,10 +1,8 @@
1
1
  require "bundler"
2
2
  Bundler.setup(:default, :development)
3
- require 'cucumber/rake/task'
4
3
  require 'rspec/core/rake_task'
5
4
 
6
- task :default => ['rake:spec', 'rake:features']
7
-
5
+ task :default => ['rake:spec']
8
6
 
9
7
  desc "Clones the Test Repository"
10
8
  task :repo do |t|
@@ -13,27 +11,9 @@ task :repo do |t|
13
11
  end
14
12
  end
15
13
 
16
- desc "Runs the Cucumber Feature Suite"
17
- Cucumber::Rake::Task.new(:features) do |t|
18
- t.cucumber_opts = ["--format pretty", "features"]
19
- end
20
- namespace :features do
21
- desc "Runs the `@current` feature(s) or scenario(s)"
22
- Cucumber::Rake::Task.new(:current) do |c|
23
- c.cucumber_opts = ["--format pretty", "-t current", "features"]
24
- end
25
- end
26
-
27
14
  desc "Runs the RSpec Test Suite"
28
15
  RSpec::Core::RakeTask.new(:spec) do |r|
29
16
  r.pattern = 'spec/*_spec.rb'
30
17
  r.rspec_opts = ['--color']
31
18
  end
32
- namespace :spec do
33
- desc "RSpec Test Suite with pretty output"
34
- RSpec::Core::RakeTask.new(:long) do |r|
35
- r.pattern = 'spec/*_spec.rb'
36
- r.rspec_opts = ['--color', '--format documentation']
37
- end
38
- end
39
19
 
data/bin/ginatra CHANGED
@@ -4,12 +4,14 @@ require "ginatra"
4
4
 
5
5
  module Ginatra::Executable
6
6
  HELP = <<HELP
7
- Usage: ginatra [ version |
7
+ Usage: ginatra [ setup |
8
+ version |
8
9
  server <options> <command> |
9
10
  daemon <command> |
10
11
  directory <command> <globs> ]
11
12
 
12
13
  Ginatra Commands:
14
+ setup - Checks Dependencies and Installs Configuration
13
15
  version - Pretty Self explanatory. Print version number and exit
14
16
 
15
17
  Ginatra Server Commands:
@@ -47,10 +49,17 @@ HELP
47
49
  load("#{path}/ginatra-server")
48
50
  end
49
51
 
52
+ def self.setup
53
+ path = File.expand_path(File.dirname(__FILE__))
54
+ load("#{path}/ginatra-setup")
55
+ end
56
+
50
57
  def self.execute(command, args)
51
58
  case command
52
59
  when "version"
53
60
  puts Ginatra::VERSION
61
+ when "setup"
62
+ setup
54
63
  when "daemon"
55
64
  daemon
56
65
  when "directory"
data/bin/ginatra-setup ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ginatra"
4
+ require "rbconfig"
5
+
6
+ if RbConfig::CONFIG["ruby_version"] < "1.9"
7
+ $stderr.puts "You need Ruby 1.9.2 to run ginatra"
8
+ exit(1)
9
+ end
10
+
11
+ if `which pygmentize` !~ /pygmentize/
12
+ $stderr.puts "You need Pygmentize to run ginatra"
13
+ exit(1)
14
+ end
15
+
16
+ if `git --version` < "git version 1.6.3"
17
+ $stderr.puts "You need at least git version 1.6.3 to run ginatra"
18
+ exit(1)
19
+ end
20
+
21
+ puts "checked deps"
22
+ Ginatra::Config.safe_setup
23
+ puts "installed config"
24
+
25
+ exit(0)
data/ginatra.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ginatra"
3
- s.version = "2.3.0"
3
+ s.version = "3.0.0"
4
4
  s.summary = "A Gitweb Clone in Sinatra and Grit"
5
5
  s.description = "Host your own git repository browser through the power of Sinatra and Grit"
6
6
  s.email = "sam@lenary.co.uk"
@@ -57,10 +57,12 @@ module Ginatra
57
57
  @logger
58
58
  end
59
59
 
60
- unless File.exist?(CONFIG_PATH)
61
- FileUtils.mkdir_p(File.dirname(CONFIG_PATH))
62
- File.open(CONFIG_PATH, 'w') do |f|
63
- YAML.dump(DEFAULT_CONFIG, f)
60
+ def self.safe_setup
61
+ unless File.exist?(CONFIG_PATH)
62
+ FileUtils.mkdir_p(File.dirname(CONFIG_PATH))
63
+ File.open(CONFIG_PATH, 'w') do |f|
64
+ YAML.dump(DEFAULT_CONFIG, f)
65
+ end
64
66
  end
65
67
  end
66
68
 
@@ -0,0 +1,77 @@
1
+ require "grit"
2
+
3
+ module Ginatra
4
+
5
+ class GraphCommit
6
+ attr_accessor :time, :space
7
+ def initialize(commit)
8
+ @_commit = commit
9
+ @time = -1
10
+ @space = 0
11
+ end
12
+
13
+ def method_missing(m, *args, &block)
14
+ @_commit.send(m, *args, &block)
15
+ end
16
+
17
+ # Method is adding time and space on the
18
+ # list of commits. As well as returns date list
19
+ # corelated with time set on commits.
20
+ #
21
+ # @param [Array<GraphCommit>] comits to index
22
+ #
23
+ # @return [Array<TimeDate>] list of commit dates corelated with time on commits
24
+ def self.index_commits(commits)
25
+ days, heads = [], []
26
+ map = {}
27
+
28
+ commits.reverse.each_with_index do |c,i|
29
+ c.time = i
30
+ days[i]=c.committed_date
31
+ map[c.id] = c
32
+ heads += c.refs unless c.refs.nil?
33
+ end
34
+
35
+ heads.select!{|h|h.is_a? Grit::Head or h.is_a? Grit::Remote}
36
+ # sort heads so the master is top and current branches are closer
37
+ heads.sort! do |a,b|
38
+ if a.name == "master"
39
+ -1
40
+ elsif b.name == "master"
41
+ 1
42
+ else
43
+ b.commit.committed_date <=> a.commit.committed_date
44
+ end
45
+ end
46
+
47
+ j=0
48
+ heads.each do |h|
49
+ if map.include? h.commit.id then
50
+ j = mark_chain(j+=1,map[h.commit.id], map)
51
+ end
52
+ end
53
+ return days
54
+ end
55
+
56
+ # Add space mark on commit and its parents
57
+ #
58
+ # @param [Fixnum] space (row on the graph) to be set
59
+ # @param [GraphCommit] the commit object.
60
+ # @param [Hash<String,GraphCommit>] map of commits
61
+ #
62
+ # @return [Fixnum] max space used.
63
+ def self.mark_chain(mark, commit, map)
64
+ commit.space = mark if commit.space == 0
65
+ m1 = mark-1
66
+ marks = commit.parents.collect do |p|
67
+ if map.include? p.id and map[p.id].space == 0 then
68
+ mark_chain(m1+=1, map[p.id],map)
69
+ else
70
+ m1+1
71
+ end
72
+ end
73
+ marks << mark
74
+ return marks.compact.max
75
+ end
76
+ end
77
+ end
data/lib/ginatra/repo.rb CHANGED
@@ -4,10 +4,15 @@ module Grit
4
4
  class Commit
5
5
  # this lets us add a link between commits and refs directly
6
6
  attr_accessor :refs
7
+
8
+ def ==(other_commit)
9
+ id == other_commit.id
10
+ end
7
11
  end
8
12
  end
9
13
 
10
14
  module Ginatra
15
+
11
16
  # A thin wrapper to the Grit::repo class so that we can add a name and a url-sanitised-name
12
17
  # to a repo, and also intercept and add refs to the commit objects.
13
18
  class Repo
@@ -27,7 +32,11 @@ module Ginatra
27
32
  @name = @param
28
33
  @description = @repo.description
29
34
  @description = "Please edit the #{@repo.path}/description file for this repository and set the description for it." if /^Unnamed repository;/.match(@description)
30
- @repo
35
+ end
36
+
37
+ def ==(other_repo)
38
+ # uses method_missing
39
+ path == other_repo.path
31
40
  end
32
41
 
33
42
  # Return a commit corresponding to the commit to the repo,
@@ -42,7 +51,7 @@ module Ginatra
42
51
  def commit(id)
43
52
  @commit = @repo.commit(id)
44
53
  raise(Ginatra::InvalidCommit.new(id)) if @commit.nil?
45
- add_refs(@commit)
54
+ add_refs(@commit,{})
46
55
  @commit
47
56
  end
48
57
 
@@ -57,19 +66,42 @@ module Ginatra
57
66
  # @return [Array<Grit::Commit>] the array of commits.
58
67
  def commits(start = 'master', max_count = 10, skip = 0)
59
68
  raise(Ginatra::Error.new("max_count cannot be less than 0")) if max_count < 0
69
+ refs_cache = {}
60
70
  @repo.commits(start, max_count, skip).each do |commit|
61
- add_refs(commit)
71
+ add_refs(commit,refs_cache)
62
72
  end
63
73
  end
64
74
 
75
+ # Return a list of commits like --all, including pagination options and all the refs.
76
+ #
77
+ # @param [Integer] max_count the maximum count of commits
78
+ # @param [Integer] skip the number of commits in the branch to skip before taking the count.
79
+ #
80
+ # @raise [Ginatra::Error] if max_count is less than 0. silly billy!
81
+ #
82
+ # @return [Array<GraphCommit>] the array of commits.
83
+ def all_commits(max_count = 10, skip = 0)
84
+ raise(Ginatra::Error.new("max_count cannot be less than 0")) if max_count < 0
85
+ commits = Grit::Commit.find_all(@repo, nil, {:max_count => max_count, :skip => skip})
86
+ ref_cache = {}
87
+ commits.collect do |commit|
88
+ add_refs(commit, ref_cache)
89
+ GraphCommit.new(commit)
90
+ end
91
+ end
65
92
  # Adds the refs corresponding to Grit::Commit objects to the respective Commit objects.
66
93
  #
67
94
  # @todo Perhaps move into commit class.
68
95
  #
69
96
  # @param [Grit::Commit] commit the commit you want refs added to
97
+ # @param [Hash] empty hash with scope out of loop to speed things up
70
98
  # @return [Array] the array of refs added to the commit. they are also on the commit object.
71
- def add_refs(commit)
72
- commit.refs = @repo.refs.select {|ref| ref.commit.id == commit.id }
99
+ def add_refs(commit, ref_cache)
100
+ if ref_cache.empty?
101
+ @repo.refs.each {|ref| ref_cache[ref.commit.id] ||= [];ref_cache[ref.commit.id] << ref}
102
+ end
103
+ commit.refs = ref_cache[commit.id] if ref_cache.include? commit.id
104
+ commit.refs ||= []
73
105
  end
74
106
 
75
107
  # Catch all
@@ -78,7 +110,11 @@ module Ginatra
78
110
  #
79
111
  # @todo update respond_to? method
80
112
  def method_missing(sym, *args, &block)
81
- @repo.send(sym, *args, &block)
113
+ if @repo.respond_to?(sym)
114
+ @repo.send(sym, *args, &block)
115
+ else
116
+ super
117
+ end
82
118
  end
83
119
 
84
120
  # to correspond to the #method_missing definition
data/lib/ginatra.rb CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  require 'sinatra/base'
3
3
  require "sinatra/partials"
4
+ require 'json'
4
5
 
5
6
  # Written myself. i know, what the hell?!
6
7
  module Ginatra
@@ -9,6 +10,7 @@ module Ginatra
9
10
  autoload :Helpers, "ginatra/helpers"
10
11
  autoload :Repo, "ginatra/repo"
11
12
  autoload :RepoList, "ginatra/repo_list"
13
+ autoload :GraphCommit, "ginatra/graph_commit"
12
14
 
13
15
  # A standard error class for inheritance.
14
16
  class Error < StandardError; end
@@ -28,7 +30,7 @@ module Ginatra
28
30
  end
29
31
  end
30
32
 
31
- VERSION = "2.3.0"
33
+ VERSION = "3.0.0"
32
34
 
33
35
  # The main application class.
34
36
  #
@@ -95,6 +97,32 @@ module Ginatra
95
97
  erb :log
96
98
  end
97
99
 
100
+ get '/:repo/graph' do
101
+ @repo = RepoList.find(params[:repo])
102
+ max_count = 650
103
+ max_count = params[:max_count].to_i unless params[:max_count].nil?
104
+ commits = @repo.all_commits(max_count)
105
+
106
+ days = GraphCommit.index_commits(commits)
107
+ @days_json = days.compact.collect{|d|[d.day,d.strftime("%b")]}.to_json
108
+ @commits_json = commits.collect do |c|
109
+ h = {}
110
+ h[:parents] = c.parents.collect do |p|
111
+ [p.id,0,0]
112
+ end
113
+ h[:author] = c.author.name.force_encoding("UTF-8")
114
+ h[:time] = c.time
115
+ h[:space] = c.space
116
+ h[:refs] = c.refs.collect{|r|r.name}.join(" ") unless c.refs.nil?
117
+ h[:id] = c.sha
118
+ h[:date] = c.date
119
+ h[:message] = c.message.force_encoding("UTF-8")
120
+ h[:login] = c.author.email
121
+ h
122
+ end.to_json
123
+ erb :graph
124
+ end
125
+
98
126
  # The atom feed of recent commits to a certain branch of a +repo+.
99
127
  #
100
128
  # @param [String] repo the repository url-sanitised-name
@@ -0,0 +1,170 @@
1
+ var commits = chunk1.commits,
2
+ comms = {},
3
+ pixelsX = [],
4
+ pixelsY = [],
5
+ mmax = Math.max,
6
+ mtime = 0,
7
+ mspace = 0,
8
+ parents = {};
9
+ for (var i = 0, ii = commits.length; i < ii; i++) {
10
+ for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
11
+ parents[commits[i].parents[j][0]] = true;
12
+ }
13
+ mtime = Math.max(mtime, commits[i].time);
14
+ mspace = Math.max(mspace, commits[i].space);
15
+ }
16
+ mtime = mtime + 4;
17
+ mspace = mspace + 10;
18
+ for (i = 0; i < ii; i++) {
19
+ if (commits[i].id in parents) {
20
+ commits[i].isParent = true;
21
+ }
22
+ comms[commits[i].id] = commits[i];
23
+ }
24
+ var colors = ["#000"];
25
+ for (var k = 0; k < mspace; k++) {
26
+ colors.push(Raphael.getColor());
27
+ }
28
+ function branchGraph(holder) {
29
+ var ch = mspace * 20 + 20, cw = mtime * 20 + 20,
30
+ r = Raphael("holder", cw, ch),
31
+ top = r.set();
32
+ var cuday = 0, cumonth = "";
33
+ r.rect(0,0,days.length*20+20,40).attr({fill: "#999"});
34
+
35
+ for (mm = 0; mm < days.length; mm++) {
36
+ if(days[mm] != null){
37
+ if(cuday != days[mm][0]){
38
+ r.text(10+mm*20,30,days[mm][0]).attr({font: "12px Fontin-Sans, Arial", fill: "#444"});
39
+ cuday = days[mm][0]
40
+ }
41
+ if(cumonth != days[mm][1]){
42
+ r.text(10+mm*20,10,days[mm][1]).attr({font: "12px Fontin-Sans, Arial", fill: "#444"});
43
+ cumonth = days[mm][1]
44
+ }
45
+
46
+ }
47
+ }
48
+ for (i = 0; i < ii; i++) {
49
+ var x = 10 + 20 * commits[i].time,
50
+ y = 70 + 20 * commits[i].space;
51
+ r.circle(x, y, 3).attr({fill: colors[commits[i].space], stroke: "none"});
52
+ if (commits[i].refs != null && commits[i].refs != "") {
53
+ var longrefs = commits[i].refs
54
+ var shortrefs = commits[i].refs;
55
+ if (shortrefs.length > 15){
56
+ shortrefs = shortrefs.substr(0,13) + "...";
57
+ }
58
+ var t = r.text(x+5,y+5,shortrefs).attr({font: "12px Fontin-Sans, Arial", fill: "#666",
59
+ title: longrefs, cursor: "pointer", rotation: "90"});
60
+
61
+ var textbox = t.getBBox();
62
+ t.translate(textbox.height/-4,textbox.width/2);
63
+ }
64
+ for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
65
+ var c = comms[commits[i].parents[j][0]];
66
+ if (c) {
67
+ var cx = 10 + 20 * c.time,
68
+ cy = 70 + 20 * c.space;
69
+ if (c.space == commits[i].space) {
70
+ r.path("M" + (x - 5) + "," + (y + .0001) + "L" + (15 + 20 * c.time) + "," + (y + .0001))
71
+ .attr({stroke: colors[c.space], "stroke-width": 2});
72
+
73
+ } else if (c.space < commits[i].space) {
74
+ r.path(["M", x - 5, y + .0001, "l-5-2,0,4,5,-2C",x-5,y,x -17, y+2, x -20, y-10,"L", cx,y-10,cx , cy])
75
+ .attr({stroke: colors[commits[i].space], "stroke-width": 2});
76
+ } else {
77
+ r.path(["M", x-5, y, "l-5-2,0,4,5,-2C",x-5,y,x -17, y-2, x -20, y+10,"L", cx,y+10,cx , cy])
78
+ .attr({stroke: colors[commits[i].space], "stroke-width": 2});
79
+ }
80
+ }
81
+ }
82
+ (function (c, x, y) {
83
+ top.push(r.circle(x, y, 10).attr({fill: "#000", opacity: 0, cursor: "pointer"})
84
+ .hover(function () {
85
+ var s = r.text(100, 100,c.author + "\n \n" +c.id + "\n \n" + c.message).attr({fill: "#fff"});
86
+ this.popup = r.popupit(x, y + 5, s, 0);
87
+ top.push(this.popup.insertBefore(this));
88
+ }, function () {
89
+ this.popup && this.popup.remove() && delete this.popup;
90
+ }));
91
+ }(commits[i], x, y));
92
+ }
93
+ top.toFront();
94
+ var hw = holder.offsetWidth,
95
+ hh = holder.offsetHeight,
96
+ v = r.rect(hw - 8, 0, 4, Math.pow(hh, 2) / ch, 2).attr({fill: "#000", opacity: 0}),
97
+ h = r.rect(0, hh - 8, Math.pow(hw, 2) / cw, 4, 2).attr({fill: "#000", opacity: 0}),
98
+ bars = r.set(v, h),
99
+ drag,
100
+ dragger = function (e) {
101
+ if (drag) {
102
+ e = e || window.event;
103
+ holder.scrollLeft = drag.sl - (e.clientX - drag.x);
104
+ holder.scrollTop = drag.st - (e.clientY - drag.y);
105
+ }
106
+ };
107
+ holder.onmousedown = function (e) {
108
+ e = e || window.event;
109
+ drag = {x: e.clientX, y: e.clientY, st: holder.scrollTop, sl: holder.scrollLeft};
110
+ document.onmousemove = dragger;
111
+ bars.animate({opacity: .5}, 300);
112
+ };
113
+ document.onmouseup = function () {
114
+ drag = false;
115
+ document.onmousemove = null;
116
+ bars.animate({opacity: 0}, 300);
117
+ };
118
+ holder.scrollLeft = cw;
119
+ };
120
+ Raphael.fn.popupit = function (x, y, set, dir, size) {
121
+ dir = dir == null ? 2 : dir;
122
+ size = size || 5;
123
+ x = Math.round(x);
124
+ y = Math.round(y);
125
+ var bb = set.getBBox(),
126
+ w = Math.round(bb.width / 2),
127
+ h = Math.round(bb.height / 2),
128
+ dx = [0, w + size * 2, 0, -w - size * 2],
129
+ dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
130
+ p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
131
+ "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
132
+ "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size,
133
+ "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
134
+ "l", -mmax(w - size, 0), 0, "z"].join(","),
135
+ xy = [{x: x, y: y + size * 2 + h}, {x: x - size * 2 - w, y: y}, {x: x, y: y - size * 2 - h}, {x: x + size * 2 + w, y: y}][dir];
136
+ set.translate(xy.x - w - bb.x, xy.y - h - bb.y);
137
+ return this.set(this.path(p).attr({fill: "#234", stroke: "none"}).insertBefore(set.node ? set : set[0]), set);
138
+ };
139
+ Raphael.fn.popup = function (x, y, text, dir, size) {
140
+ dir = dir == null ? 2 : dir > 3 ? 3 : dir;
141
+ size = size || 5;
142
+ text = text || "$9.99";
143
+ var res = this.set(),
144
+ d = 3;
145
+ res.push(this.path().attr({fill: "#000", stroke: "#000"}));
146
+ res.push(this.text(x, y, text).attr(this.g.txtattr).attr({fill: "#fff", "font-family": "Helvetica, Arial"}));
147
+ res.update = function (X, Y, withAnimation) {
148
+ X = X || x;
149
+ Y = Y || y;
150
+ var bb = this[1].getBBox(),
151
+ w = bb.width / 2,
152
+ h = bb.height / 2,
153
+ dx = [0, w + size * 2, 0, -w - size * 2],
154
+ dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
155
+ p = ["M", X - dx[dir], Y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
156
+ "l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
157
+ "l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size,
158
+ "l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
159
+ "l", -mmax(w - size, 0), 0, "z"].join(","),
160
+ xy = [{x: X, y: Y + size * 2 + h}, {x: X - size * 2 - w, y: Y}, {x: X, y: Y - size * 2 - h}, {x: X + size * 2 + w, y: Y}][dir];
161
+ xy.path = p;
162
+ if (withAnimation) {
163
+ this.animate(xy, 500, ">");
164
+ } else {
165
+ this.attr(xy);
166
+ }
167
+ return this;
168
+ };
169
+ return res.update(x, y);
170
+ };
@@ -15,6 +15,7 @@ div.active{
15
15
  border-top: 3px solid #dde;
16
16
  background: #EFEFF7;
17
17
  }
18
+ div.graph {background: #F7F7FF;}
18
19
  a { color: #333; }
19
20
  a:visited { color: #555; }
20
21
  a:hover,
@@ -0,0 +1,9 @@
1
+ #holder {
2
+ border: solid 1px #999;
3
+ cursor: move;
4
+ height: 70%;
5
+ overflow: scroll;
6
+ position: absolute;
7
+ width: auto;
8
+ margin: 6ex 3ex 0ex 0ex;
9
+ }