PageRenderTime 29ms CodeModel.GetById 15ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/util/colormap.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 156 lines | 104 code | 26 blank | 26 comment | 23 complexity | 6bb7209ef3b6471c572640e4b7d2b24e MD5 | raw file
  1# Copyright (C) 2005 Dan Loda <danloda@gmail.com>
  2
  3# This program is free software; you can redistribute it and/or modify
  4# it under the terms of the GNU General Public License as published by
  5# the Free Software Foundation; either version 2 of the License, or
  6# (at your option) any later version.
  7
  8# This program is distributed in the hope that it will be useful,
  9# but WITHOUT ANY WARRANTY; without even the implied warranty of
 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 11# GNU General Public License for more details.
 12
 13# You should have received a copy of the GNU General Public License
 14# along with this program; if not, write to the Free Software
 15# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 16import sys, math
 17
 18def _days(ctx, now):
 19    return (now - ctx.date()[0]) / (24 * 60 * 60)
 20
 21def _rescale(val, step):
 22    return float(step) * int(val / step)
 23
 24def _rescaleceil(val, step):
 25    return float(step) * math.ceil(float(val) / step)
 26
 27class AnnotateColorMap:
 28
 29    really_old_color = "#0046FF"
 30
 31    colors = {
 32        20.: "#FF0000",
 33        40.: "#FF3800",
 34        60.: "#FF7000",
 35        80.: "#FFA800",
 36        100.:"#FFE000",
 37        120.:"#E7FF00",
 38        140.:"#AFFF00",
 39        160.:"#77FF00",
 40        180.:"#3FFF00",
 41        200.:"#07FF00",
 42        220.:"#00FF31",
 43        240.:"#00FF69",
 44        260.:"#00FFA1",
 45        280.:"#00FFD9",
 46        300.:"#00EEFF",
 47        320.:"#00B6FF",
 48        340.:"#007EFF"
 49    }
 50
 51    def __init__(self, span=340.):
 52        self.set_span(span)
 53
 54    def set_span(self, span):
 55        self._span = span
 56        self._scale = span / max(self.colors.keys())
 57
 58    def get_color(self, ctx, now):
 59        color = self.really_old_color
 60        days = self.colors.keys()
 61        days.sort()
 62        days_old = _days(ctx, now)
 63        for day in days:
 64            if (days_old <= day * self._scale):
 65                color = self.colors[day]
 66                break
 67
 68        return color
 69
 70class AnnotateColorSaturation(object):
 71    def __init__(self, maxhues=None, maxsaturations=None):
 72        self._maxhues = maxhues
 73        self._maxsaturations = maxsaturations
 74
 75    def hue(self, angle):
 76        return tuple([self.v(angle, r) for r in (0, 120, 240)])
 77
 78    @staticmethod
 79    def ang(angle, rotation):
 80        angle += rotation
 81        angle = angle % 360
 82        if angle > 180:
 83            angle = 180 - (angle - 180)
 84        return abs(angle)
 85
 86    def v(self, angle, rotation):
 87        ang = self.ang(angle, rotation)
 88        if ang < 60:
 89            return 1
 90        elif ang > 120:
 91            return 0
 92        else:
 93            return 1 - ((ang - 60) / 60)
 94
 95    def saturate_v(self, saturation, hv):
 96        return int(255 - (saturation/3*(1-hv)))
 97    
 98    def committer_angle(self, committer):
 99        angle = float(abs(hash(committer))) / sys.maxint * 360.0
100        if self._maxhues is None:
101            return angle
102        return _rescale(angle, 360.0 / self._maxhues)
103
104    def get_color(self, ctx, now):
105        days = _days(ctx, now)
106        saturation = 255/((days/50) + 1)
107        if self._maxsaturations:
108            saturation = _rescaleceil(saturation, 255. / self._maxsaturations)
109        hue = self.hue(self.committer_angle(ctx.user()))
110        color = tuple([self.saturate_v(saturation, h) for h in hue])
111        return "#%x%x%x" % color
112
113def makeannotatepalette(fctxs, now, maxcolors, maxhues=None,
114                        maxsaturations=None, mindate=None):
115    """Assign limited number of colors for annotation
116
117    :fctxs: list of filecontexts by lines
118    :now: latest time which will have most significat color
119    :maxcolors: max number of colors
120    :maxhues: max number of committer angles (hues)
121    :maxsaturations: max number of saturations by age
122    :mindate: reassign palette until it includes fctx of mindate
123              (requires maxsaturations)
124
125    This returns dict of {color: fctxs, ...}.
126    """
127    if mindate is not None and maxsaturations is None:
128        raise ValueError('mindate must be specified with maxsaturations')
129
130    sortedfctxs = list(sorted(set(fctxs), key=lambda fctx: -fctx.date()[0]))
131    return _makeannotatepalette(sortedfctxs, now, maxcolors, maxhues,
132                                maxsaturations, mindate)[0]
133
134def _makeannotatepalette(sortedfctxs, now, maxcolors, maxhues,
135                         maxsaturations, mindate):
136    cm = AnnotateColorSaturation(maxhues=maxhues,
137                                 maxsaturations=maxsaturations)
138    palette = {}
139
140    def reassignifneeded(fctx):
141        # fctx is the latest fctx which is NOT included in the palette
142        if mindate is None or fctx.date()[0] < mindate or maxsaturations <= 1:
143            return palette, cm
144        return _makeannotatepalette(sortedfctxs, now, maxcolors, maxhues,
145                                    maxsaturations - 1, mindate)
146
147    # assign from the latest for maximum discrimination
148    for fctx in sortedfctxs:
149        color = cm.get_color(fctx, now)
150        if color not in palette:
151            if len(palette) >= maxcolors:
152                return reassignifneeded(fctx)
153            palette[color] = []
154        palette[color].append(fctx)
155
156    return palette, cm  # return cm for debbugging