-
Notifications
You must be signed in to change notification settings - Fork 1
/
parallel_gl.py
361 lines (290 loc) · 11.6 KB
/
parallel_gl.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import argparse
import glfw
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import pandas as pd
import numpy as np
import sys
width = 1200
height = 800
def generate_distinct_colors(n):
if n == 2:
return [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]] # Red and Blue
elif n == 3:
return [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] # Red, Green, Blue
else:
hues = np.linspace(0, 1, n + 1)[:-1] # Generate n evenly spaced hues between 0 and 1
colors = []
for hue in hues:
color = [np.sin(hue * 6.2832), np.cos(hue * 6.2832), np.sin((hue + 0.33) * 6.2832)]
colors.append([abs(x) for x in color])
return colors
def window_resize_callback(window, new_width, new_height):
global width, height # Add this line
width, height = new_width, new_height
glViewport(0, 0, width, height)
# Argument parsing
parser = argparse.ArgumentParser(description='Plot parallel coordinates from a CSV file.')
parser.add_argument('--file_path', type=str, required=True, help='Path to the CSV file.')
args = parser.parse_args()
file_path = args.file_path
zoom_factor = 1.0
center_x, center_y = 0.0, 0.0
# Add a variable to keep track of the hovered polyline
hovered_polyline = None
# Initialize GLFW
if not glfw.init():
sys.exit()
# Get the screen size
monitor = glfw.get_primary_monitor()
video_mode = glfw.get_video_mode(monitor)
screen_width, screen_height = video_mode.size
# Calculate the position to center the window
window_width, window_height = 1200, 800
pos_x = (screen_width - window_width) // 2
pos_y = (screen_height - window_height) // 2
# Create a GLFW window
window = glfw.create_window(window_width, window_height, "Parallel Coordinates", None, None)
# Check window creation
if not window:
glfw.terminate()
sys.exit()
# Center the window
glfw.set_window_pos(window, pos_x, pos_y)
INSIDE = 0 # 0000
LEFT = 1 # 0001
RIGHT = 2 # 0010
BOTTOM = 4 # 0100
TOP = 8 # 1000
def compute_outcode(x, y, xmin, ymin, xmax, ymax):
outcode = INSIDE
if x < xmin:
outcode |= LEFT
elif x > xmax:
outcode |= RIGHT
if y < ymin:
outcode |= BOTTOM
elif y > ymax:
outcode |= TOP
return outcode
def hit_test(x, y, vertex_data, num_features, num_rows, zoom_factor, zoom_center_x, zoom_center_y):
global hovered_polyline
min_distance = 0.01 # Minimum distance for highlighting
hovered_polyline = None
# Account for zoom in the hit test
x = (x - zoom_center_x) / zoom_factor + zoom_center_x
y = (y - zoom_center_y) / zoom_factor + zoom_center_y
# Define the "rectangle" around the mouse pointer
xmin, ymin = x - min_distance, y - min_distance
xmax, ymax = x + min_distance, y + min_distance
# loop over the polylines with a stride of vertices_per_polyline
for i in range(0, num_rows * 2 * (num_features - 1), 2 * (num_features - 1)):
for j in range(num_features - 1):
index = i + 2 * j
if index + 2 >= len(vertex_data):
continue
x1, y1 = vertex_data[index]
x2, y2 = vertex_data[index + 2]
outcode1 = compute_outcode(x1, y1, xmin, ymin, xmax, ymax)
outcode2 = compute_outcode(x2, y2, xmin, ymin, xmax, ymax)
accept = False
while True:
if not (outcode1 | outcode2):
accept = True
break
elif outcode1 & outcode2:
break
else:
outcode_out = outcode1 if outcode1 else outcode2
if outcode_out & TOP:
x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1)
y = ymax
elif outcode_out & BOTTOM:
x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1)
y = ymin
elif outcode_out & RIGHT:
y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1)
x = xmax
elif outcode_out & LEFT:
y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1)
x = xmin
if outcode_out == outcode1:
x1, y1 = x, y
outcode1 = compute_outcode(x1, y1, xmin, ymin, xmax, ymax)
else:
x2, y2 = x, y
outcode2 = compute_outcode(x2, y2, xmin, ymin, xmax, ymax)
if accept:
hovered_polyline = i // (2 * (num_features - 1))
return
def cursor_position_callback(window, xpos, ypos):
global center_x, center_y
center_x = (xpos / width) * 2.0 - 1.0 # Convert to NDC
center_y = 1.0 - (ypos / height) * 2.0 # Convert to NDC
# Added zoom_factor and zoom_center_x, zoom_center_y as arguments
hit_test(center_x, center_y, vertex_data, num_features, len(df_normalized), zoom_factor, 0.0, 0.0)
glfw.set_cursor_pos_callback(window, cursor_position_callback)
def scroll_callback(window, x_offset, y_offset):
global zoom_factor
zoom_factor += y_offset * 0.1
zoom_factor = max(0.1, min(15.0, zoom_factor)) # Limit zoom factor
glfw.set_scroll_callback(window, scroll_callback)
# Key callback function
def key_callback(window, key, scancode, action, mods):
if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
glfw.set_window_should_close(window, True)
elif key == glfw.KEY_W and mods == glfw.MOD_CONTROL and action == glfw.PRESS:
glfw.set_window_should_close(window, True)
# Set key callback
glfw.set_key_callback(window, key_callback)
glfw.set_window_size_callback(window, window_resize_callback)
# Make the window's context current
glfw.make_context_current(window)
# Enable anti-aliasing
glfw.window_hint(glfw.SAMPLES, 4)
glEnable(GL_MULTISAMPLE)
# use smoothest lines
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
# Shader setup
vertex_shader = """
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
uniform vec3 uniformColor;
uniform bool useUniformColor;
uniform float zoomFactor;
uniform vec2 zoomCenter;
out vec3 ourColor;
void main()
{
vec2 zoomedPos = (aPos - zoomCenter) * zoomFactor + zoomCenter;
gl_Position = vec4(zoomedPos.x, zoomedPos.y, 0.0, 1.0);
ourColor = useUniformColor ? uniformColor : aColor;
}
"""
fragment_shader = """
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
FragColor = vec4(ourColor, 1.0);
}
"""
shader = compileProgram(
compileShader(vertex_shader, GL_VERTEX_SHADER),
compileShader(fragment_shader, GL_FRAGMENT_SHADER)
)
# Read and preprocess the CSV file
def read_and_preprocess_csv(file_path):
df = pd.read_csv(file_path)
df_normalized = (df.drop(columns=['class']) - df.drop(columns=['class']).min()) / (df.drop(columns=['class']).max() - df.drop(columns=['class']).min())
df_normalized['class'] = df['class']
unique_classes = df_normalized['class'].unique()
n = len(unique_classes)
color_list = generate_distinct_colors(n)
colors = {cls: color for cls, color in zip(unique_classes, color_list)}
return df_normalized, colors
df_normalized, colors = read_and_preprocess_csv(file_path)
margin = 0.1 # Define the margin here
# After reading the CSV and normalizing it
df_normalized, colors = read_and_preprocess_csv(file_path)
num_features = len(df_normalized.columns) - 1 # Excluding 'class' column
# Function to convert DataFrame to vertex and color data
def df_to_vertex_data(df, colors):
vertices = []
vertex_colors = []
num_features = len(df.columns) - 1 # excluding 'class' column
for _, row in df.iterrows():
cls = row['class']
color = colors[cls]
for i in range(num_features - 1):
x1 = i / (num_features - 1) * 2 - 1
y1 = row[i] * 2 - 1
x2 = (i + 1) / (num_features - 1) * 2 - 1
y2 = row[i + 1] * 2 - 1
# Apply the margin
x1, x2 = x1 * (1 - margin), x2 * (1 - margin)
y1, y2 = y1 * (1 - margin), y2 * (1 - margin)
vertices.extend([(x1, y1), (x2, y2)])
vertex_colors.extend([color, color])
return np.array(vertices, dtype=np.float32), np.array(vertex_colors, dtype=np.float32)
vertex_data, color_data = df_to_vertex_data(df_normalized, colors)
# Create vertex buffer object (VBO)
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(GL_ARRAY_BUFFER, vertex_data.nbytes, vertex_data, GL_STATIC_DRAW)
# Create color buffer object (CBO)
CBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, CBO)
glBufferData(GL_ARRAY_BUFFER, color_data.nbytes, color_data, GL_STATIC_DRAW)
# Create vertex array object (VAO)
VAO = glGenVertexArrays(1)
glBindVertexArray(VAO)
# Vertex positions
glEnableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, None)
# Vertex colors
glEnableVertexAttribArray(1)
glBindBuffer(GL_ARRAY_BUFFER, CBO)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None)
# Function to create axis vertex data
def create_axis_data(num_features):
axis_vertices = []
for i in range(num_features):
x = i / (num_features - 1) * 2 - 1
# Apply the margin
x = x * (1 - margin)
axis_vertices.extend([(x, -1 + margin), (x, 1 - margin)])
return np.array(axis_vertices, dtype=np.float32)
# Generate axis vertex data
axis_vertex_data = create_axis_data(len(df_normalized.columns) - 1)
# Create axis vertex buffer object (Axis VBO)
axis_VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, axis_VBO)
glBufferData(GL_ARRAY_BUFFER, axis_vertex_data.nbytes, axis_vertex_data, GL_STATIC_DRAW)
# Create axis vertex array object (Axis VAO)
axis_VAO = glGenVertexArrays(1)
glBindVertexArray(axis_VAO)
# Axis vertex positions
glEnableVertexAttribArray(0)
glBindBuffer(GL_ARRAY_BUFFER, axis_VBO)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, None)
# Main event loop
while not glfw.window_should_close(window):
# Clear the screen
glClear(GL_COLOR_BUFFER_BIT)
# Use shader
glUseProgram(shader)
glUniform1f(glGetUniformLocation(shader, "zoomFactor"), zoom_factor)
glUniform2f(glGetUniformLocation(shader, "zoomCenter"), center_x, center_y)
# Draw parallel coordinates
glBindVertexArray(VAO)
glLineWidth(1.0) # Reset to normal line width
glUniform1i(glGetUniformLocation(shader, "useUniformColor"), 0)
glDrawArrays(GL_LINES, 0, len(vertex_data))
if hovered_polyline is not None:
# Added zoom_factor and zoom_center_x, zoom_center_y as arguments
hit_test(center_x, center_y, vertex_data, num_features, len(df_normalized), zoom_factor, 0.0, 0.0)
glLineWidth(3.0) # Increase line width
glUniform1i(glGetUniformLocation(shader, "useUniformColor"), 1)
glUniform3f(glGetUniformLocation(shader, "uniformColor"), 1.0, 1.0, 0.0)
# draw the hovered polyline
glDrawArrays(GL_LINES, hovered_polyline * (num_features - 1) * 2, (num_features - 1) * 2)
glLineWidth(1.0) # Reset to normal line width
# Draw axes in white
glBindVertexArray(axis_VAO)
glUniform1i(glGetUniformLocation(shader, "useUniformColor"), 1) # Use uniform color for axes
glUniform3f(glGetUniformLocation(shader, "uniformColor"), 1.0, 1.0, 1.0) # Set color to white in the shader
glDrawArrays(GL_LINES, 0, len(axis_vertex_data))
# Swap front and back buffers
glfw.swap_buffers(window)
# Poll for and process events
glfw.poll_events()
# Cleanup
glDeleteBuffers(1, [VBO])
glDeleteBuffers(1, [CBO])
glDeleteVertexArrays(1, [VAO])
glfw.terminate()