PageRenderTime 18ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/tortoisehg/util/colormap.py

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