Commit 79827d1a authored by Kento HASEGAWA's avatar Kento HASEGAWA

Support for the problem viewer and dragging blocks in viewers

parent fb0ef6af
...@@ -16,9 +16,9 @@ def webui_index(): ...@@ -16,9 +16,9 @@ def webui_index():
# return adc2019system.role.role # return adc2019system.role.role
return render_template('index.html') return render_template('index.html')
@webui.route('/view/solution') @webui.route('/viewer')
def webui_view_solution(): def webui_viewer():
return render_template('view-solution.html') return render_template('viewer.html')
@webui.route('/part/problems') @webui.route('/part/problems')
def webui_part_problems(): def webui_part_problems():
......
...@@ -106,16 +106,17 @@ class Host(object): ...@@ -106,16 +106,17 @@ class Host(object):
else: else:
return {'status': 'unknown request'} 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) problem = self.get_problem(problem_key)
if problem is None: if problem is None:
return None return None
problem_data = problem.get_d3json() problem_data = problem.get_d3json()
solution = problem.get_solution(solution_id) if solution_id is None:
if solution is None: solution_data = None
return None else:
solution_data = solution.get_d3json() solution = problem.get_solution(solution_id)
solution_data = solution.get_d3json()
return { return {
'problem': problem_data, 'problem': problem_data,
...@@ -146,10 +147,10 @@ class Host(object): ...@@ -146,10 +147,10 @@ class Host(object):
elif cmd == 'stop': elif cmd == 'stop':
self.worker_manager.request_stop() self.worker_manager.request_stop()
return {} return {}
elif cmd == 'view/solution': elif cmd == 'view':
problem_key = params['problem'] problem_key = params['problem']
solution_id = params['solution'] 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': elif cmd == 'adccli/login':
with open('path-to-adccli-login', 'r') as fp: with open('path-to-adccli-login', 'r') as fp:
d = json.load(fp) d = json.load(fp)
......
...@@ -103,31 +103,33 @@ class ADC2019BoardViewer { ...@@ -103,31 +103,33 @@ class ADC2019BoardViewer {
let cubeResolution = this.cubeResolution; let cubeResolution = this.cubeResolution;
// Draw outer box // Draw outer box
let _width = data['solution']['w'] * this.cubeResolution; if(('solution' in data) && (data['solution'] != null)){
let _height = data['solution']['h'] * this.cubeResolution; let _width = data['solution']['w'] * this.cubeResolution;
this.view.append("g") let _height = data['solution']['h'] * this.cubeResolution;
.attr("class", "x axis") this.view.append("g")
.selectAll("line") .attr("class", "x axis")
.data([0, _width]) .selectAll("line")
.enter().append("line") .data([0, _width])
.attr("x1", function (d) { return d; }) .enter().append("line")
.attr("y1", 0) .attr("x1", function (d) { return d; })
.attr("x2", function (d) { return d; }) .attr("y1", 0)
.attr("y2", _height) .attr("x2", function (d) { return d; })
.attr("stroke", "#d00") .attr("y2", _height)
.attr("stroke-width", 2); .attr("stroke", "#d00")
.attr("stroke-width", 2);
this.view.append("g")
.attr("class", "y axis") this.view.append("g")
.selectAll("line") .attr("class", "y axis")
.data([0, _height]) .selectAll("line")
.enter().append("line") .data([0, _height])
.attr("x1", 0) .enter().append("line")
.attr("y1", function (d) { return d; }) .attr("x1", 0)
.attr("x2", _width) .attr("y1", function (d) { return d; })
.attr("y2", function (d) { return d; }) .attr("x2", _width)
.attr("stroke", "#d00") .attr("y2", function (d) { return d; })
.attr("stroke-width", 2); .attr("stroke", "#d00")
.attr("stroke-width", 2);
}
let colors = d3.schemeCategory10 let colors = d3.schemeCategory10
...@@ -138,11 +140,18 @@ class ADC2019BoardViewer { ...@@ -138,11 +140,18 @@ class ADC2019BoardViewer {
// Create data // Create data
var blocks = data['problem']['block']; 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){ for(var bi in blocks){
let bw = blocks[bi]['w']; let bw = blocks[bi]['w'];
let bh = blocks[bi]['h']; let bh = blocks[bi]['h'];
let bx = data['solution']['block'][bi]['x']; var bx = (block_idx % block_row) * 3;
let by = data['solution']['block'][bi]['y']; 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(); blocks[bi]['cellpos'] = Array();
for(var _h=0; _h<bh; _h++){ for(var _h=0; _h<bh; _h++){
...@@ -161,6 +170,34 @@ class ADC2019BoardViewer { ...@@ -161,6 +170,34 @@ class ADC2019BoardViewer {
blocks[bi]['x'] = bx; blocks[bi]['x'] = bx;
blocks[bi]['y'] = by; blocks[bi]['y'] = by;
block_idx++;
}
function snapToGrid(p, r) {
return Math.round(p / r) * r;
}
var __view = this.view;
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);
} }
var itemContainer = this.view.selectAll("g.itemContainer") var itemContainer = this.view.selectAll("g.itemContainer")
...@@ -168,15 +205,19 @@ class ADC2019BoardViewer { ...@@ -168,15 +205,19 @@ class ADC2019BoardViewer {
.enter() .enter()
.append('g') .append('g')
.attr('class', 'itemContainer') .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-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') var cellContainer = itemContainer.append('g')
.attr('class', 'cellContainer') .attr('class', 'cellContainer')
.attr('data-color', (d, i) => colors[i % colors.length]) .attr('data-color', (d, i) => colors[i % colors.length])
.attr('x', 0) .attr('x', 0)
.attr('y', 0) .attr('y', 0);
cellContainer.selectAll('g') cellContainer.selectAll('g')
.data((d) => d.cellpos) .data((d) => d.cellpos)
...@@ -191,165 +232,100 @@ class ADC2019BoardViewer { ...@@ -191,165 +232,100 @@ class ADC2019BoardViewer {
return d3.select(nodes[i].parentNode).attr('data-color'); return d3.select(nodes[i].parentNode).attr('data-color');
}); });
cellContainer.selectAll('g') if(('solution' in data) && (data['solution'] != null)){
.data((d) => d.cellpos) cellContainer.selectAll('g')
.enter() .data((d) => d.cellpos)
.append('text') .enter()
.attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) .append('text')
.attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution)
.attr('width', this.cubeResolution) .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution)
.attr('height', this.cubeResolution) .attr('width', this.cubeResolution)
.attr('text-anchor', 'middle') .attr('height', this.cubeResolution)
.attr('fill', 'white') .attr('text-anchor', 'middle')
.attr('font-size', '20px') .attr('fill', 'white')
.attr('cursor', 'move'); .attr('font-size', '20px')
// .text((d) => d[2]); .attr('cursor', 'move');
}else{
let sw = data['solution']['w']; cellContainer.selectAll('g')
let sh = data['solution']['h']; .data((d) => d.cellpos)
var lines = Object(); .enter()
for(var _h=0; _h<sh; _h++){ .append('text')
for(var _w=0; _w<sw; _w++){ .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution)
let line_index = data['solution']['map'][_h][_w]; .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution)
if(line_index == 0){ .attr('width', this.cubeResolution)
continue; .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)){ if(('solution' in data) && (data['solution'] != null)){
lines[line_index] = Array(); // Draw lines
} let sw = data['solution']['w'];
lines[line_index].push([_w, _h, line_index]); let sh = data['solution']['h'];
if((_w+1<sw) && (data['solution']['map'][_h][_w+1] == line_index)){ var lines = Object();
lines[line_index].push([_w+0.5, _h, '']); for(var _h=0; _h<sh; _h++){
} for(var _w=0; _w<sw; _w++){
if((_h+1<sh) && (data['solution']['map'][_h+1][_w] == line_index)){ let line_index = data['solution']['map'][_h][_w];
lines[line_index].push([_w, _h+0.5, '']); if(line_index == 0){
continue;
}
if(!lines.hasOwnProperty(line_index)){
lines[line_index] = Array();
}
lines[line_index].push([_w, _h, line_index]);
if((_w+1<sw) && (data['solution']['map'][_h][_w+1] == line_index)){
lines[line_index].push([_w+0.5, _h, '']);
}
if((_h+1<sh) && (data['solution']['map'][_h+1][_w] == line_index)){
lines[line_index].push([_w, _h+0.5, '']);
}
} }
} }
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]);
} }
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(){ $(function(){
...@@ -359,17 +335,26 @@ $(function(){ ...@@ -359,17 +335,26 @@ $(function(){
hash = location.hash.replace("#", ""); hash = location.hash.replace("#", "");
params = hash.split('/'); params = hash.split('/');
problem_name = params[0];
if(params.length > 1){
solution_id = params[1];
}else{
solution_id = null;
}
$.ajax({ $.ajax({
type: "POST", type: "POST",
dataType: "json", dataType: "json",
url: "/api/view/solution", url: "/api/view",
data: JSON.stringify({ data: JSON.stringify({
"problem": params[0], "problem": problem_name,
"solution": params[1] "solution": solution_id
}), }),
contentType: 'application/json' contentType: 'application/json'
}).done((d) => { }).done((d) => {
board.draw(d); board.draw(d);
}).fail((d) => {
console.log(d);
}); });
}); });
......
...@@ -38,7 +38,7 @@ class StatusView { ...@@ -38,7 +38,7 @@ class StatusView {
_this.container.find('.solution-detail-row.valid-solution td').click((e) => { _this.container.find('.solution-detail-row.valid-solution td').click((e) => {
var solution_id = $(e.target).parent("tr").data("solution-id"); var solution_id = $(e.target).parent("tr").data("solution-id");
var problem_name = $(e.target).parent("tr").data("problem"); 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"); window.open(viewer_url, "_blank");
}); });
......
<div> <div>
<h4 class='inline-heading' id='problem-status-title'>{{problem.name}}</h4> <h4 class='inline-heading' id='problem-status-title'>{{problem.name}}</h4>
<a href="/viewer#{{problem.name}}" target="_blank">Viewer</a>
{#% # if localmode %#} {#% # if localmode %#}
<button class="btn btn-primary btn-lg start-button" type="button" data-qname="{{problem.name}}">Start</button> <button class="btn btn-primary btn-lg start-button" type="button" data-qname="{{problem.name}}">Start</button>
<!-- <button class="btn btn-danger btn-lg stop-button" type="button" data-qname="all">Stop</button> --> <!-- <button class="btn btn-danger btn-lg stop-button" type="button" data-qname="all">Stop</button> -->
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment