Created
January 16, 2017 08:28
-
-
Save stewartadam/afe7153a3e8228f75c5b6bf70a1f261e to your computer and use it in GitHub Desktop.
contour-lines-dataviz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" | |
Calculates the x,y co-ordinates of the contour lines given a 2D grid of land | |
heights, with the origin in the bottom-left corner. | |
""" | |
import math | |
# cheap scope hack | |
CONTOURS = {} | |
def add(height, x, y): | |
""" | |
Associates a known position on the coordinate grid to its land height | |
""" | |
if not height in CONTOURS: | |
CONTOURS[height] = [] | |
CONTOURS[height].append((x,y)) | |
def calculate(data, base_coord, rowWise=True): | |
""" | |
Interpolates the position of contour lines on our coordinate system where ever | |
the height is a whole integer, given known heights on the coordinate grid. | |
If rowWise=True, then we are operating on a fixed row (y-axis) and across the | |
column (x-axis) values, and vice-versa. | |
""" | |
print "%s %d" % ("Row" if rowWise else "Col", base_coord[1] if rowWise else base_coord[0]) | |
for index in range(len(data)-1): | |
start_h = data[index] | |
end_h = data[index+1] | |
delta_h = end_h - start_h | |
if int(start_h) == start_h: | |
if rowWise: | |
add(start_h, (base_coord[0]+index)*SQUARE_SIZE, base_coord[1]*SQUARE_SIZE) | |
else: | |
add(start_h, base_coord[0]*SQUARE_SIZE, (base_coord[1]+index)*SQUARE_SIZE) | |
if int(end_h) == end_h: | |
if rowWise: | |
add(end_h, (base_coord[0]+index+1)*SQUARE_SIZE, base_coord[1]*SQUARE_SIZE) | |
else: | |
add(end_h, base_coord[0]*SQUARE_SIZE, (base_coord[1]+index+1)*SQUARE_SIZE) | |
print '\t%s (%s, %s) = Height change from %.1f -> %.1f' % ("Col" if rowWise else "Row", index, index+1, start_h, end_h), | |
if abs(delta_h) < 1.0 or (abs(delta_h) == 1.0 and int(start_h) == start_h and int(end_h) == end_h): | |
print 'N/A' | |
else: | |
if delta_h > 0: | |
next_increment = int(start_h + 1) if start_h % 1 == 0 else int(math.ceil(start_h)) | |
last_increment = int(end_h - 1) if end_h % 1 == 0 else int(math.floor(end_h)) | |
else: | |
next_increment = int(start_h - 1) if start_h % 1 == 0 else int(math.floor(start_h)) | |
last_increment = int(end_h + 1) if end_h % 1 == 0 else int(math.ceil(end_h)) | |
m_per_increment = SQUARE_SIZE / delta_h | |
output = "= %.2f (%.2f/increment) | " | |
params = [delta_h, m_per_increment] | |
bounds = [next_increment, last_increment] | |
bounds.sort() | |
bounds[-1] = bounds[-1] + 1 | |
for point in range(*bounds): | |
output += "%s=%.1fm, " | |
distance = (point - start_h) * m_per_increment | |
params.append(point) | |
params.append(distance) | |
if rowWise: | |
add(point, (base_coord[0]+index)*SQUARE_SIZE+distance, base_coord[1]*SQUARE_SIZE) | |
else: | |
add(point, base_coord[0]*SQUARE_SIZE, (base_coord[1]+index)*SQUARE_SIZE+distance) | |
print output % tuple(params) | |
HEIGHTS = [ | |
[5.5, 6, 6, 6, 5.5, 5.5, 5, 5, 4.5, 4, 4, 4, 3.5, 3, 2], | |
[11, 13, 13, 12, 11.5, 10.5, 8.5, 9, 9, 8.5, 7, 7.5, 6.5, 5, 2.5], | |
[10.5, 14.5, 14, 11.5, 12.5, 12, 11, 10.5, 10.5, 9, 9, 9.5, 8.5, 7, 3.5], | |
[10.5, 14.5, 12.5, 11, 10, 10.5, 10, 9.5, 9.5, 9.5, 10, 11, 10, 8, 5], | |
[6.5, 7.5, 8, 6.5, 5.5, 4.5, 3.5, 5, 7, 8, 8.5, 9.5, 8.5, 6.5, 2.5], | |
[6, 8.5, 11, 10, 11, 8.5, 6.5, 2.5, 2, 3, 7, 8, 7, 5.5, 3], | |
[7, 11, 10, 9.5, 8.5, 8, 7.5, 5.5, 4, 2.5, 3, 4.5, 5.5, 6, 3.5], | |
[5.5, 8, 8.5, 8, 8, 8, 7.5, 7.5, 7.5, 6, 5.5, 2.5, 3, 5, 4], | |
[3.5, 6.5, 6.5, 6.5, 7.5, 7, 6.5, 6, 7.5, 7, 5.5, 4, 0.5, 1.5, 2.5], | |
[3, 7, 6.5, 5.5, 5, 5, 5.5, 6, 6.5, 6, 5, 4.5, 2.5, 0.5, 0.5], | |
[2.5, 3, 2.5, 2.5, 3.5, 3, 3.5, 2.5, 2, 2, 2, 2, 1.5, 0.5, 0], | |
] | |
SQUARE_SIZE = 10.0 | |
CONTOURS = {} | |
for i in range(len(HEIGHTS)): | |
row = HEIGHTS[i] | |
calculate(row, (0, i)) | |
for j in range(len(HEIGHTS[0])): | |
col = [row[j] for row in HEIGHTS] | |
calculate(col, (j, 0), rowWise=False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" | |
Displays visualization of contour lines based on calc.py, interactively. | |
Left/Right = Switch between heights | |
Enter = Cycle between heights automatically | |
Up/Down = Control FPS limit (animation speed) | |
c = toggle color | |
""" | |
import pygame | |
import calc | |
NUM_ROWS = len(calc.HEIGHTS) - 1 | |
NUM_COLS = len(calc.HEIGHTS[0]) - 1 | |
SCALE = 10 | |
TRANSFORM = 0.5 | |
WIDTH = (len(calc.HEIGHTS[0]) - 1) * calc.SQUARE_SIZE | |
HEIGHT = (len(calc.HEIGHTS) - 1) * calc.SQUARE_SIZE | |
FPS = 10 | |
RADIUS = 3 | |
def scale(d): | |
return int(d*SCALE) | |
def convert_xy(x, y): | |
return (scale(x), scale(HEIGHT-y)) | |
def heatmap_color(intensity): | |
if intensity <= 0.25: | |
color = (0, 0, 255*intensity*2) | |
elif intensity <= 0.5: | |
color = (0, 255*intensity, 255*intensity) | |
elif intensity <= 0.75: | |
color = (255*intensity, 255*intensity, 0) | |
else: | |
color = (255*intensity, 64*intensity, 0) | |
return color | |
clock = pygame.time.Clock() | |
pygame.init() | |
screen = pygame.display.set_mode((scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM))) | |
surface = pygame.surface.Surface((scale(WIDTH), scale(HEIGHT))) | |
current_index = calc.CONTOURS.keys()[0] | |
contour_cycle = False | |
contour_color = False | |
while True: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
quit() | |
if event.type == pygame.KEYDOWN: | |
if event.key == pygame.K_RIGHT: | |
current_index = (current_index + 1) % len(calc.CONTOURS) | |
if event.key == pygame.K_LEFT: | |
current_index = (current_index - 1) % len(calc.CONTOURS) | |
if event.key == pygame.K_UP: | |
FPS = min(FPS+2, 60) | |
if event.key == pygame.K_DOWN: | |
FPS = max(FPS-2, 5) | |
if event.key == pygame.K_RETURN: | |
contour_cycle = not contour_cycle | |
if event.key == pygame.K_c: | |
contour_color = not contour_color | |
RADIUS += 3 if contour_color else -3 | |
if contour_cycle: | |
current_index = (current_index + 1) % len(calc.CONTOURS) | |
surface.fill((0, 0, 0)) | |
for row in range(NUM_ROWS+1): | |
y = row*calc.SQUARE_SIZE | |
pygame.draw.line(surface, (128, 128, 128), convert_xy(0, y), convert_xy(WIDTH, y)) | |
for col in range(NUM_COLS+1): | |
x = col*calc.SQUARE_SIZE | |
pygame.draw.line(surface, (128, 128, 128), convert_xy(x, 0), convert_xy(x, HEIGHT)) | |
pygame.display.set_caption("height=%d" % calc.CONTOURS.keys()[current_index]) | |
keys = calc.CONTOURS.keys() | |
min_index = min(keys) | |
max_index = max(keys) | |
for pos in calc.CONTOURS[keys[current_index]]: | |
intensity = float(current_index - min_index) / (max_index - min_index) | |
x = pos[0] | |
y = pos[1] | |
color = heatmap_color(intensity) if contour_color else (255, 255, 255) | |
pygame.draw.circle(surface, color, convert_xy(x,y), RADIUS, 0) | |
scaled = pygame.transform.scale(surface, (scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM))) | |
screen.blit(scaled.convert(), (0,0)) | |
pygame.display.update() | |
clock.tick(FPS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" | |
Displays visualization of all contour lines at once with a color scale. | |
""" | |
import pygame | |
import calc | |
NUM_ROWS = len(calc.HEIGHTS) - 1 | |
NUM_COLS = len(calc.HEIGHTS[0]) - 1 | |
SCALE = 10 | |
TRANSFORM = 0.75 | |
WIDTH = (len(calc.HEIGHTS[0]) - 1) * calc.SQUARE_SIZE | |
HEIGHT = (len(calc.HEIGHTS) - 1) * calc.SQUARE_SIZE | |
FPS = 1 | |
RADIUS = 4 | |
def scale(d): | |
return int(d*SCALE) | |
def convert_xy(x, y): | |
return (scale(x), scale(HEIGHT-y)) | |
def heatmap_color(intensity): | |
if intensity <= 0.25: | |
color = (0, 0, 255*intensity*2) | |
elif intensity <= 0.5: | |
color = (0, 255*intensity, 255*intensity) | |
elif intensity <= 0.75: | |
color = (255*intensity, 255*intensity, 0) | |
else: | |
color = (255*intensity, 64*intensity, 0) | |
return color | |
clock = pygame.time.Clock() | |
pygame.init() | |
screen = pygame.display.set_mode((scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM))) | |
surface = pygame.surface.Surface((scale(WIDTH), scale(HEIGHT))) | |
while True: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
quit() | |
surface.fill((0, 0, 0)) | |
for row in range(NUM_ROWS+1): | |
y = row*calc.SQUARE_SIZE | |
pygame.draw.line(surface, (128, 128, 128), convert_xy(0, y), convert_xy(WIDTH, y)) | |
for col in range(NUM_COLS+1): | |
x = col*calc.SQUARE_SIZE | |
pygame.draw.line(surface, (128, 128, 128), convert_xy(x, 0), convert_xy(x, HEIGHT)) | |
keys = calc.CONTOURS.keys() | |
min_index = min(keys) | |
max_index = max(keys) | |
for current_index in keys: | |
intensity = float(current_index - min_index) / (max_index - min_index) | |
for pos in calc.CONTOURS[current_index]: | |
x = pos[0] | |
y = pos[1] | |
pygame.draw.circle(surface, heatmap_color(intensity), convert_xy(x,y), RADIUS, 0) | |
scaled = pygame.transform.scale(surface, (scale(WIDTH*TRANSFORM), scale(HEIGHT*TRANSFORM))) | |
screen.blit(scaled.convert(), (0,0)) | |
pygame.display.update() | |
clock.tick(FPS) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment