| 
					
				 | 
			
			
				@@ -62,8 +62,8 @@ def parse_args(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     parser.add_argument("-m", "--mode", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         choices=["burndown-project", "burndown-file", "burndown-person", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                                  "churn-matrix", "ownership", "couples-files", "couples-people", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                 "couples-shotness", "shotness", "sentiment", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                 "devs", "old-vs-new", "all", "run-times", "languages"], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                 "couples-shotness", "shotness", "sentiment", "devs", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                 "devs-efforts", "old-vs-new", "all", "run-times", "languages"], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         help="What to plot.") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     parser.add_argument( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "--resample", default="year", 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -662,9 +662,11 @@ def load_ownership(header, sequence, contents, max_people): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if people.shape[0] > max_people: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         order = numpy.argsort(-people.sum(axis=1)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        people = people[order[:max_people]] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        sequence = [sequence[i] for i in order[:max_people]] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        print("Warning: truncated people to most owning %d" % max_people) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        chosen_people = people[order[:max_people + 1]] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        chosen_people[max_people] = people[order[max_people:]].sum(axis=0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        people = chosen_people 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        sequence = [sequence[i] for i in order[:max_people]] + ["others"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        print("Warning: truncated people to the most owning %d" % max_people) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     for i, name in enumerate(sequence): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if len(name) > 40: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             sequence[i] = name[:37] + "..." 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -921,7 +923,9 @@ def plot_ownership(args, repo, names, people, date_range, last): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     matplotlib, pyplot = import_pyplot(args.backend, args.style) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pyplot.stackplot(date_range, people, labels=names) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    polys = pyplot.stackplot(date_range, people, labels=names) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if names[-1] == "others": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        polys[-1].set_hatch("/") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     pyplot.xlim(parse_date(args.start_date, date_range[0]), parse_date(args.end_date, last)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if args.relative: 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1406,6 +1410,54 @@ def show_devs(args, name, start_date, end_date, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     deploy_plot(title, args.output, args.style) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+def show_devs_efforts(args, name, start_date, end_date, data, max_people): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    from scipy.signal import convolve, slepian 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    days, people = data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    start_date = datetime.fromtimestamp(start_date) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    start_date = datetime(start_date.year, start_date.month, start_date.day) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    end_date = datetime.fromtimestamp(end_date) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    end_date = datetime(end_date.year, end_date.month, end_date.day) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    efforts_by_dev = defaultdict(int) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for day, devs in days.items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for dev, stats in devs.items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            efforts_by_dev[dev] += stats.Added + stats.Removed + stats.Changed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if len(efforts_by_dev) > max_people: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        efforts_by_dev = sorted(((v, k) for k, v in efforts_by_dev.items()), reverse=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        chosen = {v for k, v in efforts_by_dev[:max_people]} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        print("Warning: truncated people to the most active %d" % max_people) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        chosen = set(efforts_by_dev) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    chosen_efforts = sorted(((efforts_by_dev[k], k) for k in chosen), reverse=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    chosen_order = {k: i for i, (_, k) in enumerate(chosen_efforts)} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    efforts = numpy.zeros((len(chosen) + 1, (end_date - start_date).days + 1), dtype=numpy.float32) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for day, devs in days.items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for dev, stats in devs.items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            dev = chosen_order.get(dev, len(chosen_order)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            efforts[dev][day] += stats.Added + stats.Removed + stats.Changed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    window = slepian(10, 0.5) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    window /= window.sum() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for i in range(efforts.shape[0]): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        efforts[i] = convolve(efforts[i], window, "same") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    matplotlib, pyplot = import_pyplot(args.backend, args.style) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    plot_x = [start_date + timedelta(days=i) for i in range(efforts.shape[1])] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    people = [people[k] for _, k in chosen_efforts] + ["others"] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for i, name in enumerate(people): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if len(name) > 40: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            people[i] = name[:37] + "..." 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    polys = pyplot.stackplot(plot_x, efforts, labels=people) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if len(polys) == max_people + 1: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        polys[-1].set_hatch("/") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    legend = pyplot.legend(loc=2, fontsize=args.font_size) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    apply_plot_style(pyplot.gcf(), pyplot.gca(), legend, args.background, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                     args.font_size, args.size) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    deploy_plot("Efforts through time (changed lines of code)", args.output, args.style) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def show_old_vs_new(args, name, start_date, end_date, data): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     from scipy.signal import convolve, slepian 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1605,6 +1657,15 @@ def main(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         show_devs(args, reader.get_name(), *reader.get_header(), data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def devs_efforts(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            data = reader.get_devs() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        except KeyError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            print(devs_warning) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        show_devs_efforts(args, reader.get_name(), *reader.get_header(), data, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                          max_people=args.max_people) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     def old_vs_new(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             data = reader.get_devs() 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1634,6 +1695,7 @@ def main(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "shotness": shotness, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "sentiment": sentiment, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "devs": devs, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        "devs-efforts": devs_efforts, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "old-vs-new": old_vs_new, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "languages": languages, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1652,6 +1714,7 @@ def main(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         shotness() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         sentiment() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         devs() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        devs_efforts() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if web_server.running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         secs = int(os.getenv("COUPLES_SERVER_TIME", "60")) 
			 |