#include #include #include #include #include #include #include #define GLAD_GL_IMPLEMENTATION #include #undef GLAD_GL_IMPLEMENTATION #define GLFW_INCLUDE_NONE #include #define NK_INCLUDE_FIXED_TYPES #define NK_INCLUDE_STANDARD_IO #define NK_INCLUDE_DEFAULT_ALLOCATOR #define NK_INCLUDE_VERTEX_BUFFER_OUTPUT #define NK_INCLUDE_FONT_BAKING #define NK_INCLUDE_DEFAULT_FONT #define NK_SQRT sqrt #define NK_SIN sinf #define NK_COS cosf // #define NK_STRTOD strtod // #define NK_DTOA dtoa #define NK_IMPLEMENTATION #include "nuklear.h" // device (NK) implementation details static const int MAX_VERTEX_MEMORY = 512 * 1024; static const int MAX_ELEMENT_MEMORY = 128 * 1024; #ifdef __APPLE__ #define NK_SHADER_VERSION "#version 150\n" #else #define NK_SHADER_VERSION "#version 300 es\n" #endif #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "mp.h" #include "continuouscolorscale.h" #include "divergentcolorscale.h" #include "numericrange.h" #include "scatterplot.h" #include "voronoisplat.h" #include "barchart.h" #include "colormap.h" // #include "transitioncontrol.h" #include "projectionhistory.h" #include "manipulationhandler.h" #include "mapscalehandler.h" // #include "selectionhandler.h" #include "brushinghandler.h" static const int RNG_SEED = 123; static const int WINDOW_WIDTH = 1000; static const int WINDOW_HEIGHT = 800; static const std::vector COLORMAP_NAMES{ "Categorical", "Continuous", "Divergent", "Rainbow", }; static const std::vector METRIC_NAMES{ "Aggregate error", "Ctrl. Pt. influence", "Stress", }; struct media { struct nk_font *font_14; struct nk_font *font_18; struct nk_font *font_20; struct nk_font *font_22; struct nk_image unchecked; struct nk_image checked; struct nk_image rocket; struct nk_image cloud; struct nk_image pen; struct nk_image play; struct nk_image pause; struct nk_image stop; struct nk_image prev; struct nk_image next; struct nk_image tools; struct nk_image dir; struct nk_image copy; struct nk_image convert; struct nk_image del; struct nk_image edit; struct nk_image images[9]; struct nk_image menu[6]; }; struct nk_glfw_vertex { float position[2]; float uv[2]; nk_byte col[4]; }; struct device { struct nk_buffer cmds; struct nk_draw_null_texture tex_null; GLuint vbo, vao, ebo; GLuint prog; GLuint vert_shdr; GLuint frag_shdr; GLint attrib_pos; GLint attrib_uv; GLint attrib_col; GLint uniform_tex; GLint uniform_proj; GLuint font_tex; }; static void ui_header(struct nk_context *ctx, struct media *media, const char *title) { nk_style_set_font(ctx, &media->font_18->handle); nk_layout_row_dynamic(ctx, 20, 1); nk_label(ctx, title, NK_TEXT_LEFT); } static void ui_widget(struct nk_context *ctx, struct media *media, float height) { static const float ratio[] = {0.15f, 0.85f}; nk_style_set_font(ctx, &media->font_22->handle); nk_layout_row(ctx, NK_DYNAMIC, height, 2, ratio); nk_spacing(ctx, 1); } static void device_init(struct device *dev) { GLint status; static const GLchar *vertex_shader = NK_SHADER_VERSION "uniform mat4 ProjMtx;\n" "in vec2 Position;\n" "in vec2 TexCoord;\n" "in vec4 Color;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main() {\n" " Frag_UV = TexCoord;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" "}\n"; static const GLchar *fragment_shader = NK_SHADER_VERSION "precision mediump float;\n" "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "out vec4 Out_Color;\n" "void main(){\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; nk_buffer_init_default(&dev->cmds); dev->prog = glCreateProgram(); dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER); dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0); glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0); glCompileShader(dev->vert_shdr); glCompileShader(dev->frag_shdr); glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status); assert(status == GL_TRUE); glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status); assert(status == GL_TRUE); glAttachShader(dev->prog, dev->vert_shdr); glAttachShader(dev->prog, dev->frag_shdr); glLinkProgram(dev->prog); glGetProgramiv(dev->prog, GL_LINK_STATUS, &status); assert(status == GL_TRUE); dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture"); dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx"); dev->attrib_pos = glGetAttribLocation(dev->prog, "Position"); dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord"); dev->attrib_col = glGetAttribLocation(dev->prog, "Color"); { /* buffer setup */ GLsizei vs = sizeof(struct nk_glfw_vertex); size_t vp = offsetof(struct nk_glfw_vertex, position); size_t vt = offsetof(struct nk_glfw_vertex, uv); size_t vc = offsetof(struct nk_glfw_vertex, col); glGenBuffers(1, &dev->vbo); glGenBuffers(1, &dev->ebo); glGenVertexArrays(1, &dev->vao); glBindVertexArray(dev->vao); glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); glEnableVertexAttribArray((GLuint)dev->attrib_pos); glEnableVertexAttribArray((GLuint)dev->attrib_uv); glEnableVertexAttribArray((GLuint)dev->attrib_col); glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp); glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt); glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc); } glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindVertexArray(0); } static void device_upload_atlas(struct device *dev, const void *image, int width, int height) { glGenTextures(1, &dev->font_tex); glBindTexture(GL_TEXTURE_2D, dev->font_tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); } static void device_shutdown(struct device *dev) { glDetachShader(dev->prog, dev->vert_shdr); glDetachShader(dev->prog, dev->frag_shdr); glDeleteShader(dev->vert_shdr); glDeleteShader(dev->frag_shdr); glDeleteProgram(dev->prog); glDeleteTextures(1, &dev->font_tex); glDeleteBuffers(1, &dev->vbo); glDeleteBuffers(1, &dev->ebo); nk_buffer_free(&dev->cmds); } static void device_draw(struct device *dev, struct nk_context *ctx, int width, int height, struct nk_vec2 scale, enum nk_anti_aliasing AA) { GLfloat ortho[4][4] = { {2.0f, 0.0f, 0.0f, 0.0f}, {0.0f,-2.0f, 0.0f, 0.0f}, {0.0f, 0.0f,-1.0f, 0.0f}, {-1.0f,1.0f, 0.0f, 1.0f}, }; ortho[0][0] /= (GLfloat)width; ortho[1][1] /= (GLfloat)height; /* setup global state */ glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); glActiveTexture(GL_TEXTURE0); /* setup program */ glUseProgram(dev->prog); glUniform1i(dev->uniform_tex, 0); glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]); { /* convert from command queue into draw list and draw to screen */ const struct nk_draw_command *cmd; void *vertices, *elements; const nk_draw_index *offset = NULL; /* allocate vertex and element buffer */ glBindVertexArray(dev->vao); glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_MEMORY, NULL, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_MEMORY, NULL, GL_STREAM_DRAW); /* load draw vertices & elements directly into vertex + element buffer */ vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); { /* fill convert configuration */ struct nk_convert_config config; static const struct nk_draw_vertex_layout_element vertex_layout[] = { {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_glfw_vertex, position)}, {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_glfw_vertex, uv)}, {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_glfw_vertex, col)}, {NK_VERTEX_LAYOUT_END} }; NK_MEMSET(&config, 0, sizeof(config)); config.vertex_layout = vertex_layout; config.vertex_size = sizeof(struct nk_glfw_vertex); config.vertex_alignment = NK_ALIGNOF(struct nk_glfw_vertex); config.tex_null = dev->tex_null; config.circle_segment_count = 22; config.curve_segment_count = 22; config.arc_segment_count = 22; config.global_alpha = 1.0f; config.shape_AA = AA; config.line_AA = AA; /* setup buffers to load vertices and elements */ {struct nk_buffer vbuf, ebuf; nk_buffer_init_fixed(&vbuf, vertices, MAX_VERTEX_MEMORY); nk_buffer_init_fixed(&ebuf, elements, MAX_ELEMENT_MEMORY); nk_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config);} } glUnmapBuffer(GL_ARRAY_BUFFER); glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); /* iterate over and execute each draw command */ nk_draw_foreach(cmd, ctx, &dev->cmds) { if (!cmd->elem_count) continue; glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); glScissor( (GLint)(cmd->clip_rect.x * scale.x), (GLint)((height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h)) * scale.y), (GLint)(cmd->clip_rect.w * scale.x), (GLint)(cmd->clip_rect.h * scale.y)); glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); offset += cmd->elem_count; } nk_clear(ctx); nk_buffer_clear(&dev->cmds); } /* default OpenGL state */ glUseProgram(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindVertexArray(0); glDisable(GL_BLEND); glDisable(GL_SCISSOR_TEST); } class Main { public: Main(Main const&) = delete; void operator=(Main const&) = delete; static Main &instance() { static Main instance; return instance; } bool saveData() const { bool ret = true; if (m_cp.n_elem > 0 && m_indicesSavePath.size() > 0) { ret = ret && m_cp.save(m_cpSavePath, arma::raw_ascii); } if (m_cpIndices.n_elem > 0 && m_cpSavePath.size() > 0) { ret = ret && m_cpIndices.save(m_indicesSavePath, arma::raw_ascii); } return ret; } bool loadDataset(const std::string &path) { return m_X.load(path, arma::raw_ascii); } void setIndicesSavePath(const std::string &path) { m_indicesSavePath = path; } void setCPSavePath(const std::string &path) { m_cpSavePath = path; } arma::mat X() const { return m_X; } void setSelectRPs() { // cpPlot->setAcceptedMouseButtons(Qt::NoButton); // cpPlot->setAcceptHoverEvents(false); // rpPlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); // rpPlot->setAcceptHoverEvents(true); } void setSelectCPs() { // rpPlot->setAcceptedMouseButtons(Qt::NoButton); // rpPlot->setAcceptHoverEvents(false); // cpPlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); // cpPlot->setAcceptHoverEvents(true); } enum ColorScaleType { ColorScaleCategorical = 0, ColorScaleContinuous, ColorScaleDivergent, ColorScaleRainbow }; void setCPColorScale(ColorScaleType colorScaleType) { float min = 0.0f; float max = 1.0f; if (colorScaleCPs) { min = colorScaleCPs->min(); max = colorScaleCPs->max(); } colorScaleCPs = getColorScale(colorScaleType); colorScaleCPs->setExtents(min, max); // cpBarChart->setColorScale(colorScaleCPs); cpColormap->setColorScale(colorScaleCPs); // bundlePlot->setColorScale(colorScaleCPs); cpPlot->update(); } void setRPColorScale(ColorScaleType colorScaleType) { float min = 0.0f; float max = 1.0f; // TODO: if weird behaviors are found, check this (was CPs) if (colorScaleRPs) { min = colorScaleRPs->min(); max = colorScaleRPs->max(); } colorScaleRPs = getColorScale(colorScaleType); colorScaleRPs->setExtents(min, max); // rpBarChart->setColorScale(colorScaleRPs); rpColormap->setColorScale(colorScaleRPs); rpPlot->update(); splat->update(); } // Pointers to visual components whose values are set in the main() function // after components are instantiated by the QtQuick engine BarChart *cpBarChart, *rpBarChart; Colormap *cpColormap, *rpColormap; Scatterplot *cpPlot, *rpPlot; VoronoiSplat *splat; // LinePlot *bundlePlot; // Color scales in use std::shared_ptr colorScaleCPs, colorScaleRPs; // Object that controls manipulation history ProjectionHistory *projectionHistory; void undoManipulation() { projectionHistory->undo(); } void resetManipulation() { projectionHistory->reset(); } enum ObserverType { ObserverCurrent = ProjectionHistory::ObserverCurrent, ObserverDiffPrevious = ProjectionHistory::ObserverDiffPrevious, ObserverDiffFirst = ProjectionHistory::ObserverDiffFirst }; bool setObserverType(ObserverType observerType) { switch (observerType) { case ObserverCurrent: return projectionHistory->setType(ProjectionHistory::ObserverCurrent); case ObserverDiffPrevious: return projectionHistory->setType(ProjectionHistory::ObserverDiffPrevious); case ObserverDiffFirst: return projectionHistory->setType(ProjectionHistory::ObserverDiffFirst); } return false; } void setCPIndices(const arma::uvec &indices) { m_cpIndices = indices; m_rpIndices.set_size(m_X.n_rows - m_cpIndices.n_elem); NumericRange allIndices(0, m_X.n_rows); std::set_symmetric_difference(allIndices.cbegin(), allIndices.cend(), m_cpIndices.cbegin(), m_cpIndices.cend(), m_rpIndices.begin()); } void setCP(const arma::mat &cp) { if (cp.n_cols != 2 || cp.n_rows != m_cpIndices.n_elem) { return; } m_cp = cp; } void updateMap(const arma::mat &Y) { cpPlot->setXY(Y.rows(m_cpIndices)); const arma::mat ®ularPoints = Y.rows(m_rpIndices); rpPlot->setXY(regularPoints); splat->setSites(regularPoints); } private: Main() : cpPlot(0) , rpPlot(0) , splat(0) , cpBarChart(0) , rpBarChart(0) , cpColormap(0) , rpColormap(0) // , bundlePlot(0) , projectionHistory(0) { } std::shared_ptr getColorScale(ColorScaleType colorScaleType) { switch (colorScaleType) { case ColorScaleCategorical: return std::shared_ptr(new ColorScale{ // QColor("#1f77b4"), QColor("#ff7f0e"), QColor("#2ca02c"), Color(31, 119, 180), Color(255, 127, 14), Color(44, 160, 44), // QColor("#d62728"), QColor("#9467bd"), QColor("#8c564b"), Color(214, 39, 40), Color(148, 103, 189), Color(140, 86, 75), // QColor("#e377c2"), QColor("#17becf"), QColor("#7f7f7f"), Color(227, 119, 194), Color(23, 190, 207), Color(127, 127, 127), }); case ColorScaleContinuous: return std::shared_ptr( ContinuousColorScale::builtin( ContinuousColorScale::HeatedObjects, nullptr)); case ColorScaleDivergent: return std::shared_ptr( DivergentColorScale::builtin( DivergentColorScale::RedGrayBlue, nullptr)); case ColorScaleRainbow: // fall-through default: return std::shared_ptr( ContinuousColorScale::builtin( ContinuousColorScale::Rainbow, nullptr)); } } arma::mat m_X, m_cp; arma::uvec m_cpIndices, m_rpIndices; std::string m_indicesSavePath, m_cpSavePath; }; static void checkGLError(const char *msg) { GLenum error = glGetError(); std::cout << msg << " "; switch (error) { case GL_NO_ERROR: std::cout << "GL_NO_ERROR" << std::endl; break; case GL_INVALID_ENUM: std::cout << "GL_INVALID_ENUM" << std::endl; break; case GL_INVALID_VALUE: std::cout << "GL_INVALID_VALUE" << std::endl; break; case GL_INVALID_OPERATION: std::cout << "GL_INVALID_OPERATION" << std::endl; break; case GL_INVALID_FRAMEBUFFER_OPERATION: std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION" << std::endl; break; case GL_OUT_OF_MEMORY: std::cout << "GL_OUT_OF_MEMORY" << std::endl; break; } } arma::uvec extractCPs(const arma::mat &X) { int numCPs = (int) (3 * sqrt(X.n_rows)); arma::uvec indices(X.n_rows); std::iota(indices.begin(), indices.end(), 0); indices = arma::shuffle(indices); return indices.subvec(0, numCPs-1); } arma::mat standardise(const arma::mat &X) { arma::mat stdX = X; for (arma::uword j = 0; j < X.n_cols; j++) { double sd = arma::stddev(stdX.col(j)); double mean = arma::mean(stdX.col(j)); stdX.col(j) = (stdX.col(j) - mean) / sd; } return stdX; } // GLFW callbacks static void glfw_errorCallback(int err, const char *description) { std::cerr << "glfw: Error: " << err << ": " << description << std::endl; } static void text_input(GLFWwindow *win, unsigned int codepoint) { nk_input_unicode((struct nk_context *) glfwGetWindowUserPointer(win), codepoint); } static void scroll_input(GLFWwindow *win, double _, double yoff) { (void) _; nk_input_scroll((struct nk_context *) glfwGetWindowUserPointer(win), nk_vec2(0, (float) yoff)); } int main(int argc, char *argv[]) { static GLFWwindow *win; int width = 0, height = 0; int display_width = 0, display_height = 0; // GUI struct device device; struct nk_font_atlas atlas; struct media media; struct nk_context ctx; NK_UNUSED(argc); NK_UNUSED(argv); glfwSetErrorCallback(glfw_errorCallback); if (!glfwInit()) { std::cerr << "glfw: init failed" << std::endl; exit(1); } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_SCALE_TO_MONITOR, GL_TRUE); #endif glfwWindowHint(GLFW_RED_BITS, 8); glfwWindowHint(GLFW_GREEN_BITS, 8); glfwWindowHint(GLFW_BLUE_BITS, 8); glfwWindowHint(GLFW_ALPHA_BITS, 8); glfwWindowHint(GLFW_SAMPLES, 4); win = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "pm", NULL, NULL); if (!win) { std::cerr << "glfw: failed to create window" << std::endl; glfwTerminate(); exit(1); } glfwMakeContextCurrent(win); glfwSetWindowUserPointer(win, &ctx); glfwSetCharCallback(win, text_input); glfwSetScrollCallback(win, scroll_input); glfwGetWindowSize(win, &width, &height); glfwGetFramebufferSize(win, &display_width, &display_height); GLFWmonitor* primary = glfwGetPrimaryMonitor(); float xscale, yscale; glfwGetMonitorContentScale(primary, &xscale, &yscale); std::cout << "glfw: Detected scaling: " << xscale << "x" << yscale << std::endl; const GLFWvidmode * mode = glfwGetVideoMode(primary); std::cout << "glfw: Detected resolution: " << mode->width << "x" << mode->height << std::endl; if (!gladLoadGL(glfwGetProcAddress)) { std::cerr << "GLAD load failed" << std::endl; glfwTerminate(); exit(1); } glEnable(GL_MULTISAMPLE); glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); Main &m = Main::instance(); if (argc != 2 || !m.loadDataset(argv[1])) { std::cerr << "Could not load data." << std::endl; return 1; } const arma::mat &X = standardise(m.X()); arma::arma_rng::set_seed(RNG_SEED); arma::uvec cpIndices; arma::mat Ys; // Load/generate control point indices cpIndices = extractCPs(X); // Load/generate control points Ys.set_size(cpIndices.n_elem, 2); Ys.randn(); mp::forceScheme(mp::dist(X.rows(cpIndices)), Ys); if (cpIndices.n_elem != Ys.n_rows) { std::cerr << "The number of CP indices and the CP map do not match." << std::endl; return 1; } // TODO: maybe put this inside m->setCP() method; for now, will be outside // for more flexibility // Ys = arma::normalise(Ys); // Sort indices (some operations become easier later) arma::uvec cpSortedIndices = arma::sort_index(cpIndices); cpIndices = cpIndices(cpSortedIndices); Ys = Ys.rows(cpSortedIndices); m.setCPIndices(cpIndices); m.setCP(Ys); // Keeps track of what happened to the visualization ProjectionHistory history(X, cpIndices); // Visual components Colormap cpColormap, rpColormap; // cpColormap.setOrientation(Colormap::Vertical); // rpColormap.setOrientation(Colormap::Vertical); Scatterplot cpPlot, rpPlot; VoronoiSplat splat; cpPlot.setSize(512, 512); rpPlot.setSize(512, 512); rpPlot.setGlyphSize(3.0f); rpPlot.setColormap(rpColormap.texture()); splat.setSize(512, 512); splat.setColormap(rpColormap.texture()); BarChart cpBarChart, rpBarChart; m.cpPlot = &cpPlot; m.rpPlot = &rpPlot; m.splat = &splat; m.cpColormap = &cpColormap; m.rpColormap = &rpColormap; m.cpBarChart = &cpBarChart; m.rpBarChart = &rpBarChart; auto setCP = std::bind(&Main::setCP, &m, std::placeholders::_1); cpPlot.xyChanged.connect(setCP); cpPlot.xyInteractivelyChanged.connect(setCP); // Keep both scatterplots and the splat scaled equally and // relative to the full plot MapScaleHandler mapScaleHandler; mapScaleHandler.scaleChanged.connect( std::bind(&Scatterplot::setScale, &cpPlot, std::placeholders::_1, std::placeholders::_2)); mapScaleHandler.scaleChanged.connect( std::bind(&Scatterplot::setScale, &rpPlot, std::placeholders::_1, std::placeholders::_2)); mapScaleHandler.scaleChanged.connect( std::bind(&VoronoiSplat::setScale, &splat, std::placeholders::_1, std::placeholders::_2)); history.currentMapChanged.connect( std::bind(&MapScaleHandler::scaleToMap, &mapScaleHandler, std::placeholders::_1)); // Update projection as the cp are modified (either directly in // the manipulationHandler object or interactively in cpPlot ManipulationHandler manipulationHandler(X, cpIndices); cpPlot.xyInteractivelyChanged.connect( std::bind(&ManipulationHandler::setCP, &manipulationHandler, std::placeholders::_1)); // Update history whenever a new projection is computed... manipulationHandler.mapChanged.connect( std::bind(&ProjectionHistory::addMap, &history, std::placeholders::_1)); // ... and update visual components whenever the history changes history.currentMapChanged.connect( std::bind(&Main::updateMap, &m, std::placeholders::_1)); // Linking between selections /* SelectionHandler cpSelectionHandler(cpIndices.n_elem); connect(m->cpPlot, &Scatterplot::selectionInteractivelyChanged, &cpSelectionHandler, &SelectionHandler::setSelection); connect(m->cpBarChart, &BarChart::selectionInteractivelyChanged, &cpSelectionHandler, &SelectionHandler::setSelection); connect(&cpSelectionHandler, &SelectionHandler::selectionChanged, m->cpPlot, &Scatterplot::setSelection); connect(&cpSelectionHandler, &SelectionHandler::selectionChanged, m->cpBarChart, &BarChart::setSelection); connect(&cpSelectionHandler, &SelectionHandler::selectionChanged, [m](const std::vector &cpSelection) { // given some CPs, see unexpected RPs *influenced by* them std::vector selectedCPIndices; for (int i = 0; i < cpSelection.size(); i++) { if (cpSelection[i]) { selectedCPIndices.push_back(i); } } if (selectedCPIndices.empty()) { return; } arma::uvec selectedCPs(selectedCPIndices.size()); std::copy(selectedCPIndices.begin(), selectedCPIndices.end(), selectedCPs.begin()); // Only the 10% largest values, filtered by the selected RPs const arma::mat &unreliability = m->projectionHistory->unreliability(); arma::uvec indicesLargest = arma::sort_index(unreliability.cols(selectedCPs), "descending"); arma::uword numLargest = indicesLargest.n_elem * 0.01f; indicesLargest = indicesLargest.subvec(0, numLargest-1); // m->bundlePlot->setValues(unreliability(indicesLargest)); const arma::uvec &cpIndices = m->projectionHistory->cpIndices(); arma::uvec CPs = cpIndices(selectedCPs(indicesLargest / unreliability.n_rows)); const arma::uvec &rpIndices = m->projectionHistory->rpIndices(); arma::uvec RPs = indicesLargest; RPs.transform([&unreliability](arma::uword v) { return v % unreliability.n_rows; }); RPs = rpIndices(RPs); arma::uvec indices(CPs.n_elem + RPs.n_elem); for (arma::uword i = 0; i < CPs.n_elem; i++) { indices[2*i + 0] = CPs(i); indices[2*i + 1] = RPs(i); } // m->bundlePlot->setLines(indices, m->projectionHistory->Y()); }); */ // Same as before, but now for regular points /* SelectionHandler rpSelectionHandler(X.n_rows - cpIndices.n_elem); rpPlot.selectionInteractivelyChanged.connect( std::bind(&SelectionHandler::setSelection, &rpSelectionHandler)); rpBarChart.selectionInteractivelyChanged.connect( std::bind(&SelectionHandler::setSelection, &rpSelectionHandler)); rpSelectionHandler.selectionChanged.connect( std::bind(&Scatterplot::setSelection, &rpPlot)); rpSelectionHandler.selectionChanged.connect( std::bind(&BarChart::setSelection, &rpBarChart)); // This still needs more tests QObject::connect(&rpSelectionHandler, &SelectionHandler::selectionChanged, [m](const std::vector &rpSelection) { // given some RPs, see unexpected CPs *influencing* them std::vector selectedRPIndices; for (int i = 0; i < rpSelection.size(); i++) { if (rpSelection[i]) { selectedRPIndices.push_back(i); } } if (selectedRPIndices.empty()) { overviewBundles(m); return; } arma::uvec selectedRPs(selectedRPIndices.size()); std::copy(selectedRPIndices.begin(), selectedRPIndices.end(), selectedRPs.begin()); // Only the 10% largest values, filtered by the selected RPs const arma::mat &unreliability = m->projectionHistory->unreliability(); arma::uvec indicesLargest = arma::sort_index(unreliability.rows(selectedRPs), "descending"); arma::uword numLargest = indicesLargest.n_elem * 0.01f; indicesLargest = indicesLargest.subvec(0, numLargest-1); m->bundlePlot->setValues(unreliability(indicesLargest)); const arma::uvec &cpIndices = m->projectionHistory->cpIndices(); arma::uvec CPs = cpIndices(indicesLargest / selectedRPs.n_elem); const arma::uvec &rpIndices = m->projectionHistory->rpIndices(); arma::uvec RPs = indicesLargest; RPs.transform([&selectedRPs](arma::uword v) { return v % selectedRPs.n_elem; }); RPs = rpIndices(selectedRPs(RPs)); arma::uvec indices(CPs.n_elem + RPs.n_elem); for (arma::uword i = 0; i < CPs.n_elem; i++) { indices[2*i + 0] = CPs(i); indices[2*i + 1] = RPs(i); } m->bundlePlot->setLines(indices, m->projectionHistory->Y()); }); */ // Brushing between each bar chart and respective scatterplot BrushingHandler cpBrushHandler; auto cpBrushHandler_brushItem = std::bind( &BrushingHandler::brushItem, &cpBrushHandler, std::placeholders::_1); cpPlot.itemInteractivelyBrushed.connect(cpBrushHandler_brushItem); cpBarChart.itemInteractivelyBrushed.connect(cpBrushHandler_brushItem); cpBrushHandler.itemBrushed.connect( std::bind(&Scatterplot::brushItem, &cpPlot, std::placeholders::_1)); cpBrushHandler.itemBrushed.connect( std::bind(&BarChart::brushItem, &cpBarChart, std::placeholders::_1)); BrushingHandler rpBrushHandler; auto rpBrushHandler_brushItem = std::bind( &BrushingHandler::brushItem, &rpBrushHandler, std::placeholders::_1); rpPlot.itemInteractivelyBrushed.connect(rpBrushHandler_brushItem); rpBarChart.itemInteractivelyBrushed.connect(rpBrushHandler_brushItem); rpBrushHandler.itemBrushed.connect( std::bind(&Scatterplot::brushItem, &rpPlot, std::placeholders::_1)); rpBrushHandler.itemBrushed.connect( std::bind(&BarChart::brushItem, &rpBarChart, std::placeholders::_1)); // Update visual components whenever values change history.rpValuesChanged.connect([&](const arma::vec &v, bool rescale) { if (!m.colorScaleRPs || v.n_elem == 0) { return; } if (rescale) { m.colorScaleRPs->setExtents(v.min(), v.max()); } else { m.colorScaleRPs->setExtents(std::min(m.colorScaleRPs->min(), (float) v.min()), std::max(m.colorScaleRPs->max(), (float) v.max())); } m.rpColormap->setColorScale(m.colorScaleRPs); // rpPlot.setColorData(v); }); history.cpValuesChanged.connect([&](const arma::vec &v, bool rescale) { if (!m.colorScaleCPs || v.n_elem == 0) { return; } m.colorScaleCPs->setExtents(v.min(), v.max()); m.cpColormap->setColorScale(m.colorScaleCPs); // Originally, we should have done this: // // history.cpValuesChanged.connect( // std::bind(&Scatterplot::setColorData, &cpPlot, std::placeholders::_1)); // // But this has problems with the number of arguments of // setColorData, so we just call it here cpPlot.setColorData(v); }); history.rpValuesChanged.connect( std::bind(&VoronoiSplat::setValues, &splat, std::placeholders::_1)); history.cpValuesChanged.connect( std::bind(&BarChart::setValues, &cpBarChart, std::placeholders::_1)); history.rpValuesChanged.connect( std::bind(&BarChart::setValues, &rpBarChart, std::placeholders::_1)); // ProjectionHistory takes special care of separate CP/RP selections // cpSelectionHandler.selectionChanged.connect( // std::bind(&ProjectionHistory::setCPSelection, &history)); // rpSelectionHandler.selectionChanged.connect( // std::bind(&ProjectionHistory::setRPSelection, &history)); // history.selectionChanged.connect( // std::bind(&LinePlot::selectionChanged, &bundlePlot)); // Connect projection components to rewinding mechanism // TransitionControl plotTC; // plotTC.tChanged.connect( // std::bind(&ProjectionHistory::setRewind, &history, std::placeholders::_1)); history.mapRewound.connect( std::bind(&Main::updateMap, &m, std::placeholders::_1)); history.cpValuesRewound.connect( std::bind(&Scatterplot::setColorData, &cpPlot, std::placeholders::_1)); history.rpValuesRewound.connect( std::bind(&VoronoiSplat::setValues, &splat, std::placeholders::_1)); history.rpValuesRewound.connect( std::bind(&BarChart::updateValues, &rpBarChart, std::placeholders::_1)); // General component set up // plotTC->setAcceptedMouseButtons(Qt::RightButton); // m->cpPlot->setDragEnabled(true); // m->cpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); m.setSelectCPs(); // m->cpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); // m->rpBarChart->setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); m.setCPColorScale(Main::ColorScaleRainbow); enum Main::ColorScaleType cpColorScaleType = Main::ColorScaleRainbow; m.setRPColorScale(Main::ColorScaleRainbow); enum Main::ColorScaleType rpColorScaleType = Main::ColorScaleRainbow; // This sets the initial CP configuration, triggering all the necessary // signals to set up the helper objects and visual components manipulationHandler.setCP(Ys); // GUI init device_init(&device); { const void *image; int w, h; struct nk_font_config cfg = nk_font_config(0); cfg.oversample_h = 3; cfg.oversample_v = 2; nk_font_atlas_init_default(&atlas); nk_font_atlas_begin(&atlas); media.font_14 = nk_font_atlas_add_from_file(&atlas, "assets/Roboto-Regular.ttf", 14.0f, &cfg); media.font_18 = nk_font_atlas_add_from_file(&atlas, "assets/Roboto-Regular.ttf", 18.0f, &cfg); media.font_20 = nk_font_atlas_add_from_file(&atlas, "assets/Roboto-Regular.ttf", 20.0f, &cfg); media.font_22 = nk_font_atlas_add_from_file(&atlas, "assets/Roboto-Regular.ttf", 22.0f, &cfg); image = nk_font_atlas_bake(&atlas, &w, &h, NK_FONT_ATLAS_RGBA32); device_upload_atlas(&device, image, w, h); nk_font_atlas_end(&atlas, nk_handle_id((int)device.font_tex), &device.tex_null); } nk_init_default(&ctx, &media.font_14->handle); struct nk_image cpPlot_img = nk_image_id((int) cpPlot.texture()); struct nk_image rpPlot_img = nk_image_id((int) rpPlot.texture()); struct nk_image splat_img = nk_image_id((int) splat.texture()); struct nk_image cpColormap_img = nk_image_id((int) cpColormap.texture()); struct nk_image rpColormap_img = nk_image_id((int) rpColormap.texture()); while (!glfwWindowShouldClose(win)) { struct nk_vec2 scale; glfwGetWindowSize(win, &width, &height); glfwGetFramebufferSize(win, &display_width, &display_height); scale.x = static_cast(display_width) / static_cast(width); scale.y = static_cast(display_height) / static_cast(height); // Input glfwPollEvents(); { double x, y; nk_input_begin(&ctx); // glfwPollEvents(); nk_input_key(&ctx, NK_KEY_DEL, glfwGetKey(win, GLFW_KEY_DELETE) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_ENTER, glfwGetKey(win, GLFW_KEY_ENTER) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_TAB, glfwGetKey(win, GLFW_KEY_TAB) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_BACKSPACE, glfwGetKey(win, GLFW_KEY_BACKSPACE) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_LEFT, glfwGetKey(win, GLFW_KEY_LEFT) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_RIGHT, glfwGetKey(win, GLFW_KEY_RIGHT) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_UP, glfwGetKey(win, GLFW_KEY_UP) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_DOWN, glfwGetKey(win, GLFW_KEY_DOWN) == GLFW_PRESS); if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS) { nk_input_key(&ctx, NK_KEY_COPY, glfwGetKey(win, GLFW_KEY_C) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_PASTE, glfwGetKey(win, GLFW_KEY_P) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_CUT, glfwGetKey(win, GLFW_KEY_X) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_CUT, glfwGetKey(win, GLFW_KEY_E) == GLFW_PRESS); nk_input_key(&ctx, NK_KEY_SHIFT, 1); } else { nk_input_key(&ctx, NK_KEY_COPY, 0); nk_input_key(&ctx, NK_KEY_PASTE, 0); nk_input_key(&ctx, NK_KEY_CUT, 0); nk_input_key(&ctx, NK_KEY_SHIFT, 0); } glfwGetCursorPos(win, &x, &y); int int_x = static_cast(x); int int_y = static_cast(y); nk_input_motion(&ctx, int_x, int_y); nk_input_button(&ctx, NK_BUTTON_LEFT, int_x, int_y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS); nk_input_button(&ctx, NK_BUTTON_MIDDLE, int_x, int_y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS); nk_input_button(&ctx, NK_BUTTON_RIGHT, int_x, int_y, glfwGetMouseButton(win, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS); nk_input_end(&ctx); } nk_style_set_font(&ctx, &media.font_20->handle); if (nk_begin(&ctx, "Control points", nk_rect(550, 20, 350, 300), NK_WINDOW_BORDER | NK_WINDOW_TITLE)) { ui_header(&ctx, &media, "Glyphs"); nk_layout_row_dynamic(&ctx, 30, 1); float glyphSize = cpPlot.glyphSize(); nk_property_float(&ctx, "Size:", 1.0f, &glyphSize, 20.0f, 1.0f, 0.1f); if (glyphSize != cpPlot.glyphSize()) { cpPlot.setGlyphSize(glyphSize); } nk_layout_row_dynamic(&ctx, 30, 1); float opacity = 1.0f; nk_property_float(&ctx, "Opacity:", 0.0f, &opacity, 1.0f, 0.1f, 0.01f); // TODO nk_layout_row_dynamic(&ctx, 20, 1); nk_spacer(&ctx); ui_header(&ctx, &media, "Colors"); nk_layout_row_begin(&ctx, NK_DYNAMIC, 30, 2); nk_layout_row_push(&ctx, 0.3f); nk_label(&ctx, "Scale:", NK_TEXT_RIGHT); nk_layout_row_push(&ctx, 0.7f); if (nk_combo_begin_label(&ctx, COLORMAP_NAMES[cpColorScaleType].c_str(), nk_vec2(nk_widget_width(&ctx), 200))) { nk_layout_row_dynamic(&ctx, 25, 1); size_t i = 0; for (auto it = COLORMAP_NAMES.begin(); it != COLORMAP_NAMES.end(); it++) { if (nk_combo_item_label(&ctx, it->c_str(), NK_TEXT_LEFT)) { enum Main::ColorScaleType type = static_cast(i); if (type != cpColorScaleType) { m.setCPColorScale(type); cpColorScaleType = type; } } i++; } nk_combo_end(&ctx); } nk_layout_row_end(&ctx); nk_layout_row_begin(&ctx, NK_DYNAMIC, 30, 2); nk_layout_row_push(&ctx, 0.3f); nk_label(&ctx, "Map to:", NK_TEXT_RIGHT); nk_layout_row_push(&ctx, 0.7f); if (nk_combo_begin_label(&ctx, METRIC_NAMES[0].c_str(), nk_vec2(nk_widget_width(&ctx), 200))) { nk_layout_row_dynamic(&ctx, 25, 1); size_t i = 0; for (auto it = METRIC_NAMES.begin(); it != METRIC_NAMES.end(); it++) { if (nk_combo_item_label(&ctx, it->c_str(), NK_TEXT_LEFT)) { // TODO } i++; } nk_combo_end(&ctx); } nk_layout_row_end(&ctx); } nk_end(&ctx); if (nk_begin(&ctx, "Regular points", nk_rect(550, 340, 350, 400), NK_WINDOW_BORDER | NK_WINDOW_TITLE)) { ui_header(&ctx, &media, "Splat"); nk_layout_row_dynamic(&ctx, 30, 1); float alpha = splat.alpha(); nk_property_float(&ctx, "Blur (alpha):", 5.0f, &alpha, 50.0f, 1.0f, 0.1f); if (alpha != splat.alpha()) { splat.setAlpha(alpha); } nk_layout_row_dynamic(&ctx, 30, 1); float beta = splat.beta(); nk_property_float(&ctx, "Radius (beta):", 5.0f, &beta, 50.0f, 1.0f, 0.1f); if (beta != splat.beta()) { splat.setBeta(beta); } nk_layout_row_dynamic(&ctx, 20, 1); nk_spacer(&ctx); ui_header(&ctx, &media, "Glyphs"); nk_layout_row_dynamic(&ctx, 30, 1); float glyphSize = rpPlot.glyphSize(); nk_property_float(&ctx, "Size:", 1.0f, &glyphSize, 20.0f, 1.0f, 0.1f); if (glyphSize != rpPlot.glyphSize()) { rpPlot.setGlyphSize(glyphSize); } nk_layout_row_dynamic(&ctx, 30, 1); float opacity = 1.0f; nk_property_float(&ctx, "Opacity:", 0.0f, &opacity, 1.0f, 0.1f, 0.01f); // TODO nk_layout_row_dynamic(&ctx, 20, 1); nk_spacer(&ctx); ui_header(&ctx, &media, "Colors"); nk_layout_row_begin(&ctx, NK_DYNAMIC, 30, 2); nk_layout_row_push(&ctx, 0.3f); nk_label(&ctx, "Scale:", NK_TEXT_RIGHT); nk_layout_row_push(&ctx, 0.7f); if (nk_combo_begin_label(&ctx, COLORMAP_NAMES[rpColorScaleType].c_str(), nk_vec2(nk_widget_width(&ctx), 200))) { nk_layout_row_dynamic(&ctx, 25, 1); size_t i = 0; for (auto it = COLORMAP_NAMES.begin(); it != COLORMAP_NAMES.end(); it++) { if (nk_combo_item_label(&ctx, it->c_str(), NK_TEXT_LEFT)) { enum Main::ColorScaleType type = static_cast(i); if (type != rpColorScaleType) { m.setRPColorScale(type); rpColorScaleType = type; } } i++; } nk_combo_end(&ctx); } nk_layout_row_end(&ctx); nk_layout_row_begin(&ctx, NK_DYNAMIC, 30, 2); nk_layout_row_push(&ctx, 0.3f); nk_label(&ctx, "Map to:", NK_TEXT_RIGHT); nk_layout_row_push(&ctx, 0.7f); if (nk_combo_begin_label(&ctx, METRIC_NAMES[0].c_str(), nk_vec2(nk_widget_width(&ctx), 200))) { nk_layout_row_dynamic(&ctx, 25, 1); size_t i = 0; for (auto it = METRIC_NAMES.begin(); it != METRIC_NAMES.end(); it++) { if (nk_combo_item_label(&ctx, it->c_str(), NK_TEXT_LEFT)) { // TODO } i++; } nk_combo_end(&ctx); } nk_layout_row_end(&ctx); } nk_end(&ctx); struct nk_style *s = &ctx.style; nk_style_push_color(&ctx, &s->window.background, nk_rgba(0, 0, 0, 0)); nk_style_push_style_item(&ctx, &s->window.fixed_background, nk_style_item_color(nk_rgba(0,0,0,0))); if (nk_begin(&ctx, "Scatterplot", nk_rect(20, 20, splat.width(), splat.height()), NK_WINDOW_NO_SCROLLBAR)) { // Render components to their textures splat.draw(); rpPlot.draw(); cpPlot.draw(); nk_layout_space_begin(&ctx, NK_STATIC, splat.height(), 4); struct nk_rect region = nk_layout_space_bounds(&ctx); // Add white background rect and draw textures on top nk_layout_space_push(&ctx, region); struct nk_command_buffer *canvas = nk_window_get_canvas(&ctx); nk_fill_rect(canvas, region, 0.0f, nk_rgba(255, 255, 255, 255)); // Rest uses custom region region.x = region.y = 0.0f; region.w = static_cast(splat.width()); region.h = static_cast(splat.height()); nk_layout_space_push(&ctx, region); nk_image(&ctx, splat_img); nk_layout_space_push(&ctx, region); nk_image(&ctx, rpPlot_img); nk_layout_space_push(&ctx, region); nk_image(&ctx, cpPlot_img); nk_layout_space_end(&ctx); } nk_end(&ctx); nk_style_pop_color(&ctx); nk_style_pop_style_item(&ctx); if (nk_begin(&ctx, "Colormap", nk_rect(20, 40 + splat.height(), splat.width(), 120), NK_WINDOW_BORDER | NK_WINDOW_TITLE | NK_WINDOW_NO_SCROLLBAR)) { // nk_widget(&bounds, &ctx); ui_header(&ctx, &media, "Control points"); nk_layout_row_dynamic(&ctx, 10, 1); nk_image(&ctx, cpColormap_img); ui_header(&ctx, &media, "Regular points"); nk_layout_row_dynamic(&ctx, 10, 1); nk_image(&ctx, rpColormap_img); } nk_end(&ctx); glViewport(0, 0, display_width, display_height); // glDisable(GL_BLEND); glClearColor(0.3f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); device_draw(&device, &ctx, width, height, scale, NK_ANTI_ALIASING_ON); glfwSwapBuffers(win); // nk_clear(&ctx); } nk_font_atlas_clear(&atlas); nk_free(&ctx); glfwTerminate(); return 0; }