churnalizer 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +48 -0
- data/LICENCE +21 -0
- data/README.md +72 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/churnalizer.gemspec +36 -0
- data/exe/churnalizer +4 -0
- data/lib/churn_analyzers/git.rb +22 -0
- data/lib/churnalizer/analyzer.rb +65 -0
- data/lib/churnalizer/cli.rb +34 -0
- data/lib/churnalizer/version.rb +3 -0
- data/lib/churnalizer.rb +5 -0
- data/lib/complexity_analyzers/ruby.rb +18 -0
- data/lib/file_scanners/ignorer.rb +47 -0
- data/lib/file_scanners/ruby.rb +33 -0
- data/lib/graph_builders/google_charts.rb +36 -0
- data/lib/graph_builders/views/google_chart.html +75 -0
- data/screenshot.png +0 -0
- data/vendor/chartkick.js +2429 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 121e0c4906c450169d97de281a8642791ac17b5e
|
4
|
+
data.tar.gz: d13666dd80e932cee1d1556b4cb208a888ae383b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c757caeedbc38724192ddf613faf96baf3de26b37e8efd1b6fa79f4c7680321973ee9e46737a4979f9cc9a62a9d74def9ac048d3dd6ba2a1e5cf7a3ae2d06e17
|
7
|
+
data.tar.gz: 02c79cb372325593c507009f805c54ddcd4c73ff22acff093c273536cbe64646b34f5840dbb22fc9918bea9ccc8e6e425649a29bc740a744699f4d9e6e03704e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
churnalizer (0.1.0)
|
5
|
+
flog
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
byebug (10.0.0)
|
11
|
+
diff-lcs (1.3)
|
12
|
+
flog (4.6.2)
|
13
|
+
path_expander (~> 1.0)
|
14
|
+
ruby_parser (~> 3.1, > 3.1.0)
|
15
|
+
sexp_processor (~> 4.8)
|
16
|
+
path_expander (1.0.2)
|
17
|
+
rake (10.5.0)
|
18
|
+
rb-readline (0.5.5)
|
19
|
+
rspec (3.7.0)
|
20
|
+
rspec-core (~> 3.7.0)
|
21
|
+
rspec-expectations (~> 3.7.0)
|
22
|
+
rspec-mocks (~> 3.7.0)
|
23
|
+
rspec-core (3.7.1)
|
24
|
+
rspec-support (~> 3.7.0)
|
25
|
+
rspec-expectations (3.7.0)
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
+
rspec-support (~> 3.7.0)
|
28
|
+
rspec-mocks (3.7.0)
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
+
rspec-support (~> 3.7.0)
|
31
|
+
rspec-support (3.7.1)
|
32
|
+
ruby_parser (3.11.0)
|
33
|
+
sexp_processor (~> 4.9)
|
34
|
+
sexp_processor (4.10.1)
|
35
|
+
|
36
|
+
PLATFORMS
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
bundler (~> 1.16)
|
41
|
+
byebug
|
42
|
+
churnalizer!
|
43
|
+
rake (~> 10.0)
|
44
|
+
rb-readline
|
45
|
+
rspec (~> 3.0)
|
46
|
+
|
47
|
+
BUNDLED WITH
|
48
|
+
1.16.1
|
data/LICENCE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Federico Ramirez
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Churnalizer
|
2
|
+
|
3
|
+
Churnalizer helps you analyze the churn vs complexity of your Ruby application.
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
What is churn vs complexity? Sandi Metz explains it nicely in her blog post
|
8
|
+
[Breaking up the
|
9
|
+
Behemot](https://www.sandimetz.com/blog/2017/9/13/breaking-up-the-behemoth).
|
10
|
+
|
11
|
+
Basically, it shows you which files need to be refactored first -- top-right
|
12
|
+
corner of the graph. Churn is how many times a file has been changed, so you
|
13
|
+
want files which change a lot to be simple. Files which are never touched are
|
14
|
+
fine with being complex for a while.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
$ gem install churnalizer
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
$ churnalizer my-app-directory/
|
23
|
+
|
24
|
+
This was only tested on MacOS. It uses the `open` command to make things easier,
|
25
|
+
so when the gem is done analyzing your app, it will open the generated chart
|
26
|
+
with your default browser.
|
27
|
+
|
28
|
+
That functionality would not work on Linux so the chart would need to be opened
|
29
|
+
manually.
|
30
|
+
|
31
|
+
Don't think it works on Windows at all, given the churn counter uses the
|
32
|
+
following command: `cd $(dirname #{file}) && git log --oneline -- #{file} | wc -l`
|
33
|
+
|
34
|
+
### Ignoring Files
|
35
|
+
|
36
|
+
By default, Churnalizer will ignore specs and Rails files like `schema.rb` and
|
37
|
+
`routes.rb`.
|
38
|
+
|
39
|
+
For custom ignore rules, in the base directory you are analyzing, create a file
|
40
|
+
named `.churnignore`, in that file, add a regular expression per-line to run
|
41
|
+
against file paths. If a regex returns true, it will be ignored.
|
42
|
+
|
43
|
+
For example, this is the default `.churnignore` file:
|
44
|
+
|
45
|
+
/test/
|
46
|
+
/spec/
|
47
|
+
/db/
|
48
|
+
/config/
|
49
|
+
/bin/
|
50
|
+
/vendor/
|
51
|
+
/public/
|
52
|
+
|
53
|
+
## Development
|
54
|
+
|
55
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
56
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
57
|
+
prompt that will allow you to experiment.
|
58
|
+
|
59
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
60
|
+
release a new version, update the version number in `version.rb`, and then run
|
61
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
62
|
+
git commits and tags, and push the `.gem` file to
|
63
|
+
[rubygems.org](https://rubygems.org).
|
64
|
+
|
65
|
+
### Playing with the CLI
|
66
|
+
|
67
|
+
To locally run the CLI use `ruby -Ilib exe/churnalizer`
|
68
|
+
|
69
|
+
## Contributing
|
70
|
+
|
71
|
+
Bug reports and pull requests are welcome on GitHub at
|
72
|
+
https://github.com/gosukiwi/churnalizer.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "churnalizer"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/churnalizer.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "churnalizer/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "churnalizer"
|
8
|
+
spec.version = Churnalizer::VERSION
|
9
|
+
spec.authors = ["Federico Ramirez"]
|
10
|
+
spec.email = ["federico_r@beezwax.net"]
|
11
|
+
|
12
|
+
spec.summary = %q{Analyze your Ruby application for Churn vs Complexity}
|
13
|
+
spec.homepage = "http://github.com/gosukiwi/churnalizer"
|
14
|
+
|
15
|
+
if spec.respond_to?(:metadata)
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
else
|
18
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
19
|
+
"public gem pushes."
|
20
|
+
end
|
21
|
+
|
22
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
f.match(%r{^(test|spec|features)/})
|
24
|
+
end
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
spec.add_development_dependency "byebug"
|
33
|
+
spec.add_development_dependency "rb-readline"
|
34
|
+
|
35
|
+
spec.add_dependency "flog"
|
36
|
+
end
|
data/exe/churnalizer
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module ChurnAnalyzers
|
2
|
+
# This class uses Git to analyze the churn (how many times it was changed) of
|
3
|
+
# a file.
|
4
|
+
#
|
5
|
+
class Git
|
6
|
+
def analyze(file)
|
7
|
+
as_integer run_command(file)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Trim string and cast to integer
|
13
|
+
def as_integer(string)
|
14
|
+
string.gsub(/[\n ]+/, "").to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
# NOTE: This only works on *NIX systems
|
18
|
+
def run_command(file)
|
19
|
+
`cd $(dirname #{file}) && git log --oneline -- #{file} | wc -l`
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "file_scanners/ruby"
|
2
|
+
require "churn_analyzers/git"
|
3
|
+
require "complexity_analyzers/ruby"
|
4
|
+
require "graph_builders/google_charts"
|
5
|
+
|
6
|
+
module Churnalizer
|
7
|
+
class Analyzer
|
8
|
+
attr_reader :path
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
build_graph analyzed_files
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def analyzed_files
|
20
|
+
files.map do |file|
|
21
|
+
[display_name_for(file), analyze(file)]
|
22
|
+
end.to_h
|
23
|
+
end
|
24
|
+
|
25
|
+
def display_name_for(file)
|
26
|
+
file.gsub(path, ".")
|
27
|
+
end
|
28
|
+
|
29
|
+
def analyze(file)
|
30
|
+
{ churn: churn_for(file), complexity: complexity_for(file) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def files
|
34
|
+
file_scanner.scan
|
35
|
+
end
|
36
|
+
|
37
|
+
def file_scanner
|
38
|
+
@file_scanner ||= FileScanners::Ruby.new(path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def churn_analyzer
|
42
|
+
@churn_analyzer ||= ChurnAnalyzers::Git.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def churn_for(file)
|
46
|
+
churn_analyzer.analyze(file)
|
47
|
+
end
|
48
|
+
|
49
|
+
def complexity_analyzer
|
50
|
+
@complexity_analyzer ||= ComplexityAnalyzers::Ruby.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def complexity_for(file)
|
54
|
+
complexity_analyzer.analyze(file)
|
55
|
+
end
|
56
|
+
|
57
|
+
def graph_builder
|
58
|
+
@graph_builder ||= GraphBuilders::GoogleCharts.new
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_graph(graph_data)
|
62
|
+
graph_builder.build(graph_data, save_to: "chart.html")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "churnalizer/analyzer"
|
2
|
+
|
3
|
+
module Churnalizer
|
4
|
+
class CLI
|
5
|
+
attr_accessor :path
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@path = path
|
9
|
+
analyze
|
10
|
+
end
|
11
|
+
|
12
|
+
def analyze
|
13
|
+
return help if path.nil?
|
14
|
+
puts churnalizer.run
|
15
|
+
end
|
16
|
+
|
17
|
+
def churnalizer
|
18
|
+
@churnalizer ||= Churnalizer::Analyzer.new(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def help
|
22
|
+
puts """This is Churnalizer, a churn vs complexity analyzer for your Ruby
|
23
|
+
application.
|
24
|
+
|
25
|
+
Usage:
|
26
|
+
churnalizer my-app/
|
27
|
+
"""
|
28
|
+
end
|
29
|
+
|
30
|
+
def path
|
31
|
+
ARGV[0]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/churnalizer.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "flog"
|
2
|
+
|
3
|
+
module ComplexityAnalyzers
|
4
|
+
# This class uses Flog <https://github.com/seattlerb/flog> to check the
|
5
|
+
# complexity of a Ruby file.
|
6
|
+
#
|
7
|
+
class Ruby
|
8
|
+
def analyze(file)
|
9
|
+
flog.reset
|
10
|
+
flog.flog(file)
|
11
|
+
flog.total_score
|
12
|
+
end
|
13
|
+
|
14
|
+
def flog
|
15
|
+
@flog ||= Flog.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module FileScanners
|
2
|
+
# Takes a list of files and ignores them appropriately
|
3
|
+
#
|
4
|
+
class Ignorer
|
5
|
+
DEFAULT_IGNORES = %w[/test/ /spec/ /db/ /config/ /bin/ /vendor/ /public/].freeze
|
6
|
+
|
7
|
+
attr_reader :path
|
8
|
+
def initialize(path)
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
|
12
|
+
def ignore?(file)
|
13
|
+
ignore_rules.each do |rule|
|
14
|
+
return true if rule === file
|
15
|
+
end
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def ignore_rules
|
22
|
+
@ignore_rules ||= churnignore.map { |regex| Regexp.new(regex) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def churnignore
|
26
|
+
if churnignore_contents.empty?
|
27
|
+
DEFAULT_IGNORES
|
28
|
+
else
|
29
|
+
churnignore_as_array
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def churnignore_as_array
|
34
|
+
churnignore_contents.split("\n").compact
|
35
|
+
end
|
36
|
+
|
37
|
+
def churnignore_contents
|
38
|
+
@churnignore_contents ||= File.read(churnignore_path)
|
39
|
+
rescue Errno::ENOENT
|
40
|
+
""
|
41
|
+
end
|
42
|
+
|
43
|
+
def churnignore_path
|
44
|
+
"#{path}/.churnignore"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "file_scanners/ignorer"
|
2
|
+
|
3
|
+
module FileScanners
|
4
|
+
# FileScanners are in charge of finding files to be analyzed. This one in
|
5
|
+
# particular finds Ruby files.
|
6
|
+
#
|
7
|
+
class Ruby
|
8
|
+
attr_reader :path
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def scan
|
14
|
+
files.reject do |file|
|
15
|
+
ignore? file
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def files
|
22
|
+
Dir.glob("#{path}/**/*.rb")
|
23
|
+
end
|
24
|
+
|
25
|
+
def ignorer
|
26
|
+
@ignorer ||= Ignorer.new(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def ignore?(file)
|
30
|
+
ignorer.ignore? file
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module GraphBuilders
|
4
|
+
# Generate an HTML chart using Google Charts
|
5
|
+
#
|
6
|
+
class GoogleCharts
|
7
|
+
def build(graph_data, save_to:)
|
8
|
+
write_chart graph_data, to_file: save_to
|
9
|
+
open_with_default_browser save_to
|
10
|
+
File.expand_path save_to
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def open_with_default_browser(file)
|
16
|
+
`open #{file}` if `which open` == "/usr/bin/open\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_chart(graph_data, to_file:)
|
20
|
+
contents = compile_template graph_data
|
21
|
+
File.write(to_file, contents)
|
22
|
+
end
|
23
|
+
|
24
|
+
def compile_template(graph_data)
|
25
|
+
template.gsub("{{graph_data}}", graph_data.to_json)
|
26
|
+
end
|
27
|
+
|
28
|
+
def template
|
29
|
+
File.read view_path("google_chart.html")
|
30
|
+
end
|
31
|
+
|
32
|
+
def view_path(name)
|
33
|
+
"#{__dir__}/views/#{name}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<title>Churnalizer: Churn vs Complexity</title>
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"
|
9
|
+
}
|
10
|
+
h1,
|
11
|
+
h2 {
|
12
|
+
text-align: center;
|
13
|
+
}
|
14
|
+
|
15
|
+
p {
|
16
|
+
text-align: center;
|
17
|
+
margin: 0 auto 1.25em;
|
18
|
+
max-width: 780px;
|
19
|
+
line-height: 1.2;
|
20
|
+
}
|
21
|
+
</style>
|
22
|
+
</head>
|
23
|
+
<body>
|
24
|
+
<h1>Churn vs Complexity</h1>
|
25
|
+
|
26
|
+
<div id="churnalizer-chart"></div>
|
27
|
+
|
28
|
+
<h2>How to read this chart?</h2>
|
29
|
+
<p>
|
30
|
+
Basically, you want to minimize the amount of files which show up at the
|
31
|
+
top-right of the chart. Those are files which are complex and also change
|
32
|
+
a lot.
|
33
|
+
</p>
|
34
|
+
<p>
|
35
|
+
Ideally, you don't want to have complex files, but if you do, at least make
|
36
|
+
sure they don't change often. The files at the top-right are good candidates
|
37
|
+
for refactoring.
|
38
|
+
</p>
|
39
|
+
|
40
|
+
<script src="https://www.gstatic.com/charts/loader.js"></script>
|
41
|
+
<script>
|
42
|
+
google.charts.load('current', {'packages':['corechart']});
|
43
|
+
google.charts.setOnLoadCallback(drawChart);
|
44
|
+
|
45
|
+
var graph_data = {{graph_data}};
|
46
|
+
|
47
|
+
function drawChart() {
|
48
|
+
var data = new google.visualization.DataTable();
|
49
|
+
data.addColumn('number', 'Churn');
|
50
|
+
data.addColumn('number', 'Complexity');
|
51
|
+
data.addColumn({ type: 'string', role: 'tooltip' });
|
52
|
+
|
53
|
+
for(var file in graph_data) {
|
54
|
+
file_data = graph_data[file]
|
55
|
+
data.addRow([
|
56
|
+
file_data.churn,
|
57
|
+
file_data.complexity,
|
58
|
+
file + "\nComplexity: " + Math.round(file_data.complexity) + "\nChurn: " + file_data.churn
|
59
|
+
]);
|
60
|
+
}
|
61
|
+
|
62
|
+
var options = {
|
63
|
+
hAxis: {title: 'Churn', minValue: 0, maxValue: 50},
|
64
|
+
vAxis: {title: 'Complexity', minValue: 0, maxValue: 200},
|
65
|
+
legend: 'none',
|
66
|
+
crosshair: { trigger: 'both' }
|
67
|
+
};
|
68
|
+
|
69
|
+
var chart = new google.visualization.ScatterChart(document.getElementById('churnalizer-chart'));
|
70
|
+
|
71
|
+
chart.draw(data, options);
|
72
|
+
}
|
73
|
+
</script>
|
74
|
+
</body>
|
75
|
+
</html>
|
data/screenshot.png
ADDED
Binary file
|