選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

160 行
7.1 KiB

  1. import argparse
  2. sample_usage = """example:
  3. python traitograph.py --names a b c --values 1 2 3 --max-value 4
  4. python traitograph.py -o "character.png" --names n1 n2 n3 n4 --values 1 3 2 3 --max-value 4
  5. python traitograph.py --names curious organized energetic friendly confident --values 5 4 3 4 2 --max-value 7 -d True
  6. """
  7. parser = argparse.ArgumentParser(description="Generate a chart showing character traits.",
  8. epilog=sample_usage,
  9. formatter_class=argparse.RawDescriptionHelpFormatter)
  10. parser.add_argument("--out-file", "-o", dest="out_file_name",
  11. metavar="out_file", default="chart.png",
  12. help="The file in which the image will be written. (Defaults to \"chart.png\")")
  13. parser.add_argument("--names", "-n", dest="names",
  14. required=True, nargs="+", metavar="name",
  15. help="The names of the character traits")
  16. parser.add_argument("--values", "-v", dest="values",
  17. required=True, nargs="+", type=int, metavar="value",
  18. help="The values for the character traits")
  19. parser.add_argument("--max-value", "-m", dest="max_value",
  20. required=True, type=int, metavar="max",
  21. help="The maximum value for any trait")
  22. parser.add_argument("--foreground", "-fg", dest="foreground",
  23. nargs=3, type=int, default=[0,0,0], metavar="channel",
  24. help="The foreground color of the chart. (Values from 0 to 255 -- defaults to [0,0,0])")
  25. parser.add_argument("--backgournd", "-bg", dest="background",
  26. nargs=3, type=int, default=[255,255,255], metavar="channel",
  27. help="The background color of the chart. (Values from 0 to 255 -- defaults to [255,255,255])")
  28. parser.add_argument("--trait-color", "-lc", dest="trait_color",
  29. nargs=4, type=int, default=[0,190,190,100], metavar="channel",
  30. help="The color of the colored part of the chart. (Values from 0 to 255 -- defaults to [0,190,190,100])")
  31. parser.add_argument("--outer-padding", "-op", dest="outer_padding",
  32. type=int, default=20, metavar="pixel_count",
  33. help="The additional padding applied around the chart. (Defaults to 20)")
  34. parser.add_argument("--line-spacing", "-ls", dest="line_spacing",
  35. type=int, default=40, metavar="pixel_count",
  36. help="The additional padding applied around the chart. (Defaults to 40)")
  37. parser.add_argument("--dot-size", "-ds", dest="dot_size",
  38. type=int, default=2, metavar="dot_size",
  39. help="The size of the dots. (Defaults to 2)")
  40. parser.add_argument("--display", "-d", dest="display",
  41. type=bool, default=False, metavar="display",
  42. help="If True, shows the generated plot in a window. (Defaults to False)")
  43. args = parser.parse_args()
  44. import math
  45. import os
  46. os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
  47. import pygame
  48. from pygame import gfxdraw
  49. # to have the first vertex at the top rather than the bottom, otherwise
  50. # odd-numbered n-gons look like they are upside down
  51. circle_offset = math.pi
  52. def generate_n_gon_points(n, center, radii):
  53. alpha_step = math.tau / n
  54. if type(radii) == int:
  55. radii = [radii for i in range(n)]
  56. else:
  57. assert(len(radii) == n)
  58. # go clockwise, so flip the sign in the sin and cos, functions (I know
  59. # unnecessary for cos, but looks better when aligned :)
  60. return [(int(math.sin(-i * alpha_step + circle_offset) * radii[i] + center[0]), # x coordintate
  61. int(math.cos(-i * alpha_step + circle_offset) * radii[i] + center[1])) # y coordintate
  62. for i in range(n)]
  63. def generate_plot_surface(dimension_count, values, max_value, line_spacing, foreground, background, trait_color, dot_size):
  64. # figure out exactly, how big the surface needs to be, by looking at the
  65. # outer most points
  66. radius = line_spacing * max_value
  67. points = generate_n_gon_points(dimension_count, (0,0), radius)
  68. max_x = max((point[0] for point in points))
  69. min_x = -max_x # symmetry: fist point is always straight up
  70. min_y = -radius # fist point is always straight up
  71. max_y = max((point[1] for point in points))
  72. padding = dot_size//2 + 4
  73. surface_size = (max_x - min_x + 2*(padding),
  74. max_y - min_y + 2*(padding))
  75. plot_center = [surface_size[0] // 2,
  76. radius + padding]
  77. # generate the surface
  78. surface = pygame.Surface(surface_size)
  79. surface.fill(background)
  80. transparent_overlay = pygame.Surface(surface_size, pygame.SRCALPHA)
  81. transparent_overlay.set_alpha(100) #TODO(Felix): make this adjustable
  82. font = pygame.font.Font(pygame.font.get_default_font(), int(line_spacing*0.4))
  83. for i in range(max_value):
  84. points = generate_n_gon_points(dimension_count, plot_center, radius)
  85. pygame.draw.aalines(surface, foreground, True, points)
  86. for point in points:
  87. gfxdraw.filled_circle(surface, point[0], point[1], dot_size, foreground)
  88. gfxdraw.aacircle(surface, point[0], point[1], dot_size, foreground)
  89. textsurface = font.render(f"{max_value-i}", True, foreground)
  90. text_location = (points[0][0] - textsurface.get_size()[0] // 2,
  91. points[0][1] + int(line_spacing * 0.2))
  92. surface.blit(textsurface,text_location)
  93. radius -= line_spacing
  94. # draw in the trait polygon
  95. radii = [value * line_spacing for value in values]
  96. trait_points = generate_n_gon_points(dimension_count, plot_center, radii)
  97. print(trait_points)
  98. trait_color_full_alpha = trait_color[:3]
  99. for point in trait_points:
  100. gfxdraw.filled_circle(surface, point[0], point[1], 4, trait_color_full_alpha)
  101. pygame.draw.aalines(surface, trait_color_full_alpha, True, trait_points)
  102. # draw the transparent polygon
  103. pygame.draw.polygon(transparent_overlay, trait_color, trait_points)
  104. surface.blit(transparent_overlay, (0,0))
  105. return surface
  106. def plot(names, values, max_value, foreground, background, trait_color, out_file_name, display, outer_padding, line_spacing, dot_size):
  107. number_dimensions = len(names)
  108. assert(number_dimensions == len(values))
  109. assert(max_value > 0)
  110. pygame.init()
  111. plot_surface = generate_plot_surface(number_dimensions, values, max_value, line_spacing, foreground, background, trait_color, dot_size)
  112. final_surface = plot_surface
  113. pygame.image.save(final_surface, out_file_name)
  114. if display:
  115. window = pygame.display.set_mode(final_surface.get_size())
  116. pygame.display.set_caption("Traitograph")
  117. window.blit(final_surface, (0,0))
  118. pygame.display.update()
  119. running = True
  120. while running:
  121. for event in pygame.event.get():
  122. if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
  123. running = False
  124. else:
  125. print("done")
  126. pygame.quit()
  127. plot(**vars(args))