scripts/last-dotplot
changeset 878 20f5c97a3cfd
parent 877 f5f93e51ef53
child 895 8eb06508b3ed
     1.1 --- a/scripts/last-dotplot	Tue Oct 03 11:10:47 2017 +0900
     1.2 +++ b/scripts/last-dotplot	Tue Oct 03 18:25:07 2017 +0900
     1.3 @@ -163,13 +163,17 @@
     1.4      parts[1::2] = map(int, parts[1::2])
     1.5      return parts
     1.6  
     1.7 -def get_text_sizes(my_strings, font, fontsize, image_mode):
     1.8 +def textDimensions(imageDraw, font, textRot, text):
     1.9 +    x, y = imageDraw.textsize(text, font=font)
    1.10 +    return (y, x) if textRot else (x, y)
    1.11 +
    1.12 +def get_text_sizes(my_strings, font, fontsize, image_mode, textRot):
    1.13      '''Get widths & heights, in pixels, of some strings.'''
    1.14      if fontsize == 0: return [(0, 0) for i in my_strings]
    1.15      image_size = 1, 1
    1.16      im = Image.new(image_mode, image_size)
    1.17      draw = ImageDraw.Draw(im)
    1.18 -    return [draw.textsize(i, font=font) for i in my_strings]
    1.19 +    return [textDimensions(draw, font, textRot, i) for i in my_strings]
    1.20  
    1.21  def sizeText(size):
    1.22      suffixes = "bp", "kb", "Mb", "Gb"
    1.23 @@ -184,7 +188,7 @@
    1.24      return seqName + ": " + sizeText(seqSize)
    1.25  
    1.26  def getSeqInfo(sortOpt, seqNames, seqLimits,
    1.27 -               font, fontsize, image_mode, isShowSize):
    1.28 +               font, fontsize, image_mode, isShowSize, textRot):
    1.29      '''Return miscellaneous information about the sequences.'''
    1.30      if sortOpt == 1:
    1.31          seqNames.sort(key=natural_sort_key)
    1.32 @@ -202,8 +206,9 @@
    1.33          seqLabels = map(seqNameAndSizeText, seqNames, seqSizes)
    1.34      else:
    1.35          seqLabels = seqNames
    1.36 -    labelSizes = get_text_sizes(seqLabels, font, fontsize, image_mode)
    1.37 -    margin = max(zip(*labelSizes)[1])  # maximum text height
    1.38 +    labelSizes = get_text_sizes(seqLabels, font, fontsize, image_mode, textRot)
    1.39 +    margin = max(i[1] for i in labelSizes)
    1.40 +    # xxx the margin may be too big, because some labels may get omitted
    1.41      return seqNames, seqSizes, seqLabels, labelSizes, margin
    1.42  
    1.43  def div_ceil(x, y):
    1.44 @@ -413,11 +418,11 @@
    1.45  
    1.46  def make_label(text, text_size, range_start, range_size):
    1.47      '''Return an axis label with endpoint & sort-order information.'''
    1.48 -    text_width  = text_size[0]
    1.49 +    text_width, text_height = text_size
    1.50      label_start = range_start + (range_size - text_width) // 2
    1.51      label_end   = label_start + text_width
    1.52      sort_key    = text_width - range_size
    1.53 -    return sort_key, label_start, label_end, text
    1.54 +    return sort_key, label_start, label_end, text, text_height
    1.55  
    1.56  def get_nonoverlapping_labels(labels, label_space):
    1.57      '''Get a subset of non-overlapping axis labels, greedily.'''
    1.58 @@ -428,21 +433,23 @@
    1.59              nonoverlapping_labels.append(i)
    1.60      return nonoverlapping_labels
    1.61  
    1.62 -def get_axis_image(seqNames, name_sizes, seq_starts, seq_pix,
    1.63 -                   font, image_mode, opts):
    1.64 +def get_axis_image(seqNames, name_sizes, seq_starts, seq_pix, textRot,
    1.65 +                   textAln, font, image_mode, opts):
    1.66      '''Make an image of axis labels.'''
    1.67      min_pos = seq_starts[0]
    1.68      max_pos = seq_starts[-1] + seq_pix[-1]
    1.69 -    height = max(zip(*name_sizes)[1])
    1.70 +    margin = max(i[1] for i in name_sizes)
    1.71      labels = map(make_label, seqNames, name_sizes, seq_starts, seq_pix)
    1.72      labels = [i for i in labels if i[1] >= min_pos and i[2] <= max_pos]
    1.73      labels.sort()
    1.74 -    labels = get_nonoverlapping_labels(labels, opts.label_space)
    1.75 -    image_size = max_pos, height
    1.76 +    minPixTweenLabels = 0 if textRot else opts.label_space
    1.77 +    labels = get_nonoverlapping_labels(labels, minPixTweenLabels)
    1.78 +    image_size = (margin, max_pos) if textRot else (max_pos, margin)
    1.79      im = Image.new(image_mode, image_size, opts.border_color)
    1.80      draw = ImageDraw.Draw(im)
    1.81      for i in labels:
    1.82 -        position = i[1], 0
    1.83 +        base = margin - i[4] if textAln else 0
    1.84 +        position = (base, i[1]) if textRot else (i[1], base)
    1.85          draw.text(position, i[3], font=font, fill=opts.text_color)
    1.86      return im
    1.87  
    1.88 @@ -466,12 +473,14 @@
    1.89      warn("done")
    1.90      if not alignments: raise Exception("there are no alignments")
    1.91  
    1.92 +    textRot1 = "vertical".startswith(opts.rot1)
    1.93      i1 = getSeqInfo(opts.sort1, seqNames1, seqLimits1,
    1.94 -                    font, opts.fontsize, image_mode, opts.lengths1)
    1.95 +                    font, opts.fontsize, image_mode, opts.lengths1, textRot1)
    1.96      seqNames1, seqSizes1, seqLabels1, labelSizes1, tMargin = i1
    1.97  
    1.98 +    textRot2 = "horizontal".startswith(opts.rot2)
    1.99      i2 = getSeqInfo(opts.sort2, seqNames2, seqLimits2,
   1.100 -                    font, opts.fontsize, image_mode, opts.lengths2)
   1.101 +                    font, opts.fontsize, image_mode, opts.lengths2, textRot2)
   1.102      seqNames2, seqSizes2, seqLabels2, labelSizes2, lMargin = i2
   1.103  
   1.104      warn("choosing bp per pixel...")
   1.105 @@ -530,10 +539,13 @@
   1.106  
   1.107      if opts.fontsize != 0:
   1.108          axis1 = get_axis_image(seqLabels1, labelSizes1, seq_starts1, seq_pix1,
   1.109 -                               font, image_mode, opts)
   1.110 +                               textRot1, False, font, image_mode, opts)
   1.111 +        if textRot1:
   1.112 +            axis1 = axis1.transpose(Image.ROTATE_90)
   1.113          axis2 = get_axis_image(seqLabels2, labelSizes2, seq_starts2, seq_pix2,
   1.114 -                               font, image_mode, opts)
   1.115 -        axis2 = axis2.transpose(Image.ROTATE_270)  # !!! bug hotspot
   1.116 +                               textRot2, textRot2, font, image_mode, opts)
   1.117 +        if not textRot2:
   1.118 +            axis2 = axis2.transpose(Image.ROTATE_270)
   1.119          im.paste(axis1, (0, 0))
   1.120          im.paste(axis2, (0, 0))
   1.121  
   1.122 @@ -592,6 +604,10 @@
   1.123                    help="TrueType or OpenType font file")
   1.124      og.add_option("-s", "--fontsize", metavar="SIZE", type="int", default=11,
   1.125                    help="TrueType or OpenType font size (default: %default)")
   1.126 +    og.add_option("--rot1", metavar="ROT", default="h",
   1.127 +                  help="text rotation for the 1st genome (default=%default)")
   1.128 +    og.add_option("--rot2", metavar="ROT", default="v",
   1.129 +                  help="text rotation for the 2nd genome (default=%default)")
   1.130      og.add_option("--lengths1", action="store_true",
   1.131                    help="show sequence lengths for the 1st (horizontal) genome")
   1.132      og.add_option("--lengths2", action="store_true",