ginatra 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }