From 79827d1aa1cc77fc623e0055db9274c09f22e377 Mon Sep 17 00:00:00 2001 From: Kento HASEGAWA Date: Wed, 21 Aug 2019 23:03:16 +0900 Subject: [PATCH] Support for the problem viewer and dragging blocks in viewers --- main.py | 6 +- roles/host.py | 15 +- static/js/adc2019-viewer.js | 357 +++++++++--------- static/js/adc2019.js | 2 +- templates/part_problem_status.html | 1 + templates/{view-solution.html => viewer.html} | 0 6 files changed, 184 insertions(+), 197 deletions(-) rename templates/{view-solution.html => viewer.html} (100%) diff --git a/main.py b/main.py index 051d039..2582dac 100644 --- a/main.py +++ b/main.py @@ -16,9 +16,9 @@ def webui_index(): # return adc2019system.role.role return render_template('index.html') -@webui.route('/view/solution') -def webui_view_solution(): - return render_template('view-solution.html') +@webui.route('/viewer') +def webui_viewer(): + return render_template('viewer.html') @webui.route('/part/problems') def webui_part_problems(): diff --git a/roles/host.py b/roles/host.py index f0e6b39..3c1840c 100644 --- a/roles/host.py +++ b/roles/host.py @@ -106,16 +106,17 @@ class Host(object): else: return {'status': 'unknown request'} - def get_solution_for_viewer(self, problem_key, solution_id): + def get_viewer_data(self, problem_key, solution_id): problem = self.get_problem(problem_key) if problem is None: return None problem_data = problem.get_d3json() - solution = problem.get_solution(solution_id) - if solution is None: - return None - solution_data = solution.get_d3json() + if solution_id is None: + solution_data = None + else: + solution = problem.get_solution(solution_id) + solution_data = solution.get_d3json() return { 'problem': problem_data, @@ -146,10 +147,10 @@ class Host(object): elif cmd == 'stop': self.worker_manager.request_stop() return {} - elif cmd == 'view/solution': + elif cmd == 'view': problem_key = params['problem'] solution_id = params['solution'] - return self.get_solution_for_viewer(problem_key, solution_id) + return self.get_viewer_data(problem_key, solution_id) elif cmd == 'adccli/login': with open('path-to-adccli-login', 'r') as fp: d = json.load(fp) diff --git a/static/js/adc2019-viewer.js b/static/js/adc2019-viewer.js index 1860822..84a6bed 100644 --- a/static/js/adc2019-viewer.js +++ b/static/js/adc2019-viewer.js @@ -103,31 +103,33 @@ class ADC2019BoardViewer { let cubeResolution = this.cubeResolution; // Draw outer box - let _width = data['solution']['w'] * this.cubeResolution; - let _height = data['solution']['h'] * this.cubeResolution; - this.view.append("g") - .attr("class", "x axis") - .selectAll("line") - .data([0, _width]) - .enter().append("line") - .attr("x1", function (d) { return d; }) - .attr("y1", 0) - .attr("x2", function (d) { return d; }) - .attr("y2", _height) - .attr("stroke", "#d00") - .attr("stroke-width", 2); - - this.view.append("g") - .attr("class", "y axis") - .selectAll("line") - .data([0, _height]) - .enter().append("line") - .attr("x1", 0) - .attr("y1", function (d) { return d; }) - .attr("x2", _width) - .attr("y2", function (d) { return d; }) - .attr("stroke", "#d00") - .attr("stroke-width", 2); + if(('solution' in data) && (data['solution'] != null)){ + let _width = data['solution']['w'] * this.cubeResolution; + let _height = data['solution']['h'] * this.cubeResolution; + this.view.append("g") + .attr("class", "x axis") + .selectAll("line") + .data([0, _width]) + .enter().append("line") + .attr("x1", function (d) { return d; }) + .attr("y1", 0) + .attr("x2", function (d) { return d; }) + .attr("y2", _height) + .attr("stroke", "#d00") + .attr("stroke-width", 2); + + this.view.append("g") + .attr("class", "y axis") + .selectAll("line") + .data([0, _height]) + .enter().append("line") + .attr("x1", 0) + .attr("y1", function (d) { return d; }) + .attr("x2", _width) + .attr("y2", function (d) { return d; }) + .attr("stroke", "#d00") + .attr("stroke-width", 2); + } let colors = d3.schemeCategory10 @@ -138,11 +140,18 @@ class ADC2019BoardViewer { // Create data var blocks = data['problem']['block']; + var block_nums = Object.keys(blocks).length; + var block_row = Math.ceil(Math.sqrt(block_nums)); + var block_idx = 0; for(var bi in blocks){ let bw = blocks[bi]['w']; let bh = blocks[bi]['h']; - let bx = data['solution']['block'][bi]['x']; - let by = data['solution']['block'][bi]['y']; + var bx = (block_idx % block_row) * 3; + var by = (Math.floor(block_idx / block_row)) * 3; + if(('solution' in data) && (data['solution'] != null)){ + bx = data['solution']['block'][bi]['x']; + by = data['solution']['block'][bi]['y']; + } blocks[bi]['cellpos'] = Array(); for(var _h=0; _h { + return 'translate(' + snapToGrid(_x, cubeResolution) + ',' + snapToGrid(_y, cubeResolution) + ')' + }) + } + function dragended(d) { + var el = d3.select(this).classed("dragging", false); + var _x = parseInt(el.attr('data-x'), 10) + d3.event.x; + var _y = parseInt(el.attr('data-y'), 10) + d3.event.y; + d3.select(this) + .attr('data-x', snapToGrid(_x, cubeResolution)) + .attr('data-y', snapToGrid(_y, cubeResolution)); + } + function dragstarted(d) { + var el = d3.select(this); + el.raise().classed("dragging", true); } var itemContainer = this.view.selectAll("g.itemContainer") @@ -168,15 +205,19 @@ class ADC2019BoardViewer { .enter() .append('g') .attr('class', 'itemContainer') - .attr("transform", (d) => 'translate(' + d.x + ',' + d.y + ')') + .attr("transform", (d) => 'translate(' + 0 + ',' + 0 + ')') .attr('data-x', (d) => d.x) - .attr('data-y', (d) => d.y); + .attr('data-y', (d) => d.y) + .call(d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended)); var cellContainer = itemContainer.append('g') .attr('class', 'cellContainer') .attr('data-color', (d, i) => colors[i % colors.length]) .attr('x', 0) - .attr('y', 0) + .attr('y', 0); cellContainer.selectAll('g') .data((d) => d.cellpos) @@ -191,165 +232,100 @@ class ADC2019BoardViewer { return d3.select(nodes[i].parentNode).attr('data-color'); }); - cellContainer.selectAll('g') - .data((d) => d.cellpos) - .enter() - .append('text') - .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) - .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) - .attr('width', this.cubeResolution) - .attr('height', this.cubeResolution) - .attr('text-anchor', 'middle') - .attr('fill', 'white') - .attr('font-size', '20px') - .attr('cursor', 'move'); - // .text((d) => d[2]); - - let sw = data['solution']['w']; - let sh = data['solution']['h']; - var lines = Object(); - for(var _h=0; _h d.cellpos) + .enter() + .append('text') + .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) + .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) + .attr('width', this.cubeResolution) + .attr('height', this.cubeResolution) + .attr('text-anchor', 'middle') + .attr('fill', 'white') + .attr('font-size', '20px') + .attr('cursor', 'move'); + }else{ + cellContainer.selectAll('g') + .data((d) => d.cellpos) + .enter() + .append('text') + .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) + .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) + .attr('width', this.cubeResolution) + .attr('height', this.cubeResolution) + .attr('text-anchor', 'middle') + .attr('fill', 'white') + .attr('font-size', '20px') + .attr('cursor', 'move') + .text((d) => d[2]); + } - if(!lines.hasOwnProperty(line_index)){ - lines[line_index] = Array(); - } - lines[line_index].push([_w, _h, line_index]); - if((_w+1 lineColors[i % lineColors.length]); + + lineContainer.selectAll('g') + .data((d) => d) + .enter() + .append('rect') + .attr('x', (d) => (d[0]+0.25) * this.cubeResolution) + .attr('y', (d) => (d[1]+0.25) * this.cubeResolution) + .attr('width', (this.cubeResolution*0.5)) + .attr('height', (this.cubeResolution*0.5)) + .attr('fill', (d, i, nodes) => { + return d3.select(nodes[i].parentNode).attr('data-color'); + }); + + lineContainer.selectAll('g') + .data((d) => d) + .enter() + .append('text') + .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) + .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) + .attr('width', (this.cubeResolution*0.5)) + .attr('height', (this.cubeResolution*0.5)) + .attr('text-anchor', 'middle') + .attr('fill', 'black') + .attr('font-size', '16px') + .text((d) => d[2]); } - - let lineColors = d3.schemePastel2 - var lineItemContainer = this.view.selectAll("g.lineItemContainer") - .data(Object.values(lines)) - .enter() - .append('g') - .attr('class', 'lineItemContainer'); - - var lineContainer = lineItemContainer.append('g') - .attr('class', 'lineContainer') - .attr('data-color', (d, i) => lineColors[i % lineColors.length]); - - lineContainer.selectAll('g') - .data((d) => d) - .enter() - .append('rect') - .attr('x', (d) => (d[0]+0.25) * this.cubeResolution) - .attr('y', (d) => (d[1]+0.25) * this.cubeResolution) - .attr('width', (this.cubeResolution*0.5)) - .attr('height', (this.cubeResolution*0.5)) - .attr('fill', (d, i, nodes) => { - return d3.select(nodes[i].parentNode).attr('data-color'); - }); - - lineContainer.selectAll('g') - .data((d) => d) - .enter() - .append('text') - .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) - .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) - .attr('width', (this.cubeResolution*0.5)) - .attr('height', (this.cubeResolution*0.5)) - .attr('text-anchor', 'middle') - .attr('fill', 'black') - .attr('font-size', '16px') - .text((d) => d[2]); - } - // _draw_blocks(blocks){ - - // let cubeResolution = this.cubeResolution; - - // function snapToGrid(p, r) { - // return Math.round(p / r) * r; - // } - - // function dragged(d) { - // var el = d3.select(this); - // var _x = parseInt(el.attr('data-x'), 10) + d3.event.x; - // var _y = parseInt(el.attr('data-y'), 10) + d3.event.y; - // el.attr("transform", (d) => { - // return 'translate(' + snapToGrid(_x, cubeResolution) + ',' + snapToGrid(_y, cubeResolution) + ')' - // }) - // } - // function dragended(d) { - // var el = d3.select(this).classed("dragging", false); - // var _x = parseInt(el.attr('data-x'), 10) + d3.event.x; - // var _y = parseInt(el.attr('data-y'), 10) + d3.event.y; - // d3.select(this) - // .attr('data-x', snapToGrid(_x, cubeResolution)) - // .attr('data-y', snapToGrid(_y, cubeResolution)); - // } - // function dragstarted(d) { - // var el = d3.select(this); - // el.raise().classed("dragging", true); - // } - - // let colors = d3.schemeCategory10 - - // // Reference - // // Grid: https://bl.ocks.org/ngminhtrung/7c5721a1504f3e29a36da9ddd9e5039b - // // Snap: https://bl.ocks.org/evanjmg/ea3e59e67b4256c8831d3fc80f71294b - // // Nested data structure: https://codeday.me/jp/qa/20190428/720184.html - - // var itemContainer = this.view.selectAll("g.itemContainer") - // .data(blocks) - // .enter() - // .append('g') - // .attr('class', 'itemContainer') - // .attr("transform", (d) => 'translate(' + d.x + ',' + d.y + ')') - // .attr('data-x', (d) => d.x) - // .attr('data-y', (d) => d.y) - // .call(d3.drag() - // .on("start", dragstarted) - // .on("drag", dragged) - // .on("end", dragended)); - - // var cellContainer = itemContainer.append('g') - // .attr('class', 'cellContainer') - // .attr('data-color', (d, i) => colors[i]) - // .attr('x', 0) - // .attr('y', 0) - - // cellContainer.selectAll('g') - // .data((d) => d.cells) - // .enter() - // .append('rect') - // .attr('x', (d) => d[0] * this.cubeResolution) - // .attr('y', (d) => d[1] * this.cubeResolution) - // .attr('width', this.cubeResolution) - // .attr('height', this.cubeResolution) - // .attr('cursor', 'move') - // .attr('fill', (d, i, nodes) => { - // return d3.select(nodes[i].parentNode).attr('data-color'); - // }); - - // cellContainer.selectAll('g') - // .data((d) => d.cells) - // .enter() - // .append('text') - // .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) - // .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) - // .attr('width', this.cubeResolution) - // .attr('height', this.cubeResolution) - // .attr('text-anchor', 'middle') - // .attr('fill', 'white') - // .attr('font-size', '20px') - // .attr('cursor', 'move') - // .text((d) => d[2]); - - // } } $(function(){ @@ -359,17 +335,26 @@ $(function(){ hash = location.hash.replace("#", ""); params = hash.split('/'); + problem_name = params[0]; + if(params.length > 1){ + solution_id = params[1]; + }else{ + solution_id = null; + } + $.ajax({ type: "POST", dataType: "json", - url: "/api/view/solution", + url: "/api/view", data: JSON.stringify({ - "problem": params[0], - "solution": params[1] + "problem": problem_name, + "solution": solution_id }), contentType: 'application/json' }).done((d) => { board.draw(d); + }).fail((d) => { + console.log(d); }); }); diff --git a/static/js/adc2019.js b/static/js/adc2019.js index a4a751b..3692338 100644 --- a/static/js/adc2019.js +++ b/static/js/adc2019.js @@ -38,7 +38,7 @@ class StatusView { _this.container.find('.solution-detail-row.valid-solution td').click((e) => { var solution_id = $(e.target).parent("tr").data("solution-id"); var problem_name = $(e.target).parent("tr").data("problem"); - var viewer_url = "/view/solution#" + problem_name + "/" + solution_id; + var viewer_url = "/viewer#" + problem_name + "/" + solution_id; window.open(viewer_url, "_blank"); }); diff --git a/templates/part_problem_status.html b/templates/part_problem_status.html index 56e9595..eca4f65 100644 --- a/templates/part_problem_status.html +++ b/templates/part_problem_status.html @@ -1,5 +1,6 @@

{{problem.name}}

+ Viewer {#% # if localmode %#} diff --git a/templates/view-solution.html b/templates/viewer.html similarity index 100% rename from templates/view-solution.html rename to templates/viewer.html -- 2.22.0