Commit c2566bbc authored by Kento HASEGAWA's avatar Kento HASEGAWA
Browse files

Add solution viewer

parent 2dcebcae
Loading
Loading
Loading
Loading
+4 −0
Changes for main.py: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@ 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('/part/problems')
def webui_part_problems():
    if (adc2019system.role is not None) and (adc2019system.role.type == 'host'):
+20 −0
Changes for roles/host.py: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -95,6 +95,22 @@ class Host(object):
        else:
            return {'status': 'unknown request'}
    
    def get_solution_for_viewer(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()
        
        return {
            'problem': problem_data,
            'solution': solution_data
        }

    def call_api(self, method, cmd, params):
        if cmd == 'role':
            # サーバの役割確認
@@ -109,6 +125,10 @@ class Host(object):
        elif cmd == 'request/status':
            request_id = float(params['request_id'])
            return self.get_request_status(request_id)
        elif cmd == 'view/solution':
            problem_key = params['problem']
            solution_id = params['solution']
            return self.get_solution_for_viewer(problem_key, solution_id)
        else:
            return None

+377 −0
Changes for static/js/adc2019-viewer.js: 377 added lines, 0 removed lines.
Original line number Diff line number Diff line

class ADC2019BoardViewer {

    constructor(selector) {
        this.container = $(selector);
        this.width = this.container.width();
        this.height = $(window).height();

        this.svg = d3.select(selector).append('svg');
        this.view = this.svg.append('g').attr('class', 'view');

        this.currentTransform = null;

        this.cubeResolution = 50;

        this._init();
        this._draw_slider();
    }

    draw(data){
        this._draw_init();
        // this._get_board(data);
        
        this._draw_grid(data['problem']['w'], data['problem']['h']);
        this._draw_solution(data);
    }

    _init(){
        var _self = this;

        if (this.currentTransform){
            this.view.attr('transform', this.currentTransform);
        }
    }

    _draw_init(){
        this.view.selectAll("g").remove();
    }

    _draw_grid(x_cnt, y_cnt){
        let _width = x_cnt * this.cubeResolution;
        let _height = y_cnt * this.cubeResolution;
        this.view.append("g")
            .attr("class", "x axis")
            .selectAll("line")
            .data(d3.range(0, _width+1, this.cubeResolution))
            .enter().append("line")
            .attr("x1", function (d) { return d; })
            .attr("y1", 0)
            .attr("x2", function (d) { return d; })
            .attr("y2", _height)
            .attr("stroke", "#888");

        this.view.append("g")
            .attr("class", "y axis")
            .selectAll("line")
            .data(d3.range(0, _height+1, this.cubeResolution))
            .enter().append("line")
            .attr("x1", 0)
            .attr("y1", function (d) { return d; })
            .attr("x2", _width)
            .attr("y2", function (d) { return d; })
            .attr("stroke", "#888");
    }

    _draw_slider(){

        let _self = this;

        var zoom = d3.zoom()
            .scaleExtent([0.5, 5])
            .translateExtent([
                [-this.width * 2, -this.height * 2],
                [this.width * 2, this.height * 2]
                ])
            .on("zoom", zoomed);

        function zoomed() {
            this.currentTransform = d3.event.transform;
            _self.view.attr("transform", this.currentTransform);
            // this.slider.property("value", d3.event.scale);
        }

        function slided(d) {
            zoom.scaleTo(this.svg, d3.select(this).property("value"));
        }
        
        // var slider = d3.select("body").append("input")
        //     .datum({})
        //     .attr("type", "range")
        //     .attr("value", 1)
        //     .attr("min", zoom.scaleExtent()[0])
        //     .attr("max", zoom.scaleExtent()[1])
        //     .attr("step", (zoom.scaleExtent()[1] - zoom.scaleExtent()[0]) / 100)
        //     .on("input", slided);

        this.svg.call(zoom).on('dblclick.zoom', null);

    }

    _draw_solution(data){

        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);


        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

        // Create data
        var blocks = data['problem']['block'];
        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'];

            blocks[bi]['cellpos'] = Array();
            for(var _h=0; _h<bh; _h++){
                for(var _w=0; _w<bw; _w++){
                    if(blocks[bi]['cells'][_h][_w] != 0){
                        var _text;
                        if(blocks[bi]['cells'][_h][_w] == "+"){
                            _text = "";
                        }else{
                            _text = "" + blocks[bi]['cells'][_h][_w];
                        }
                        blocks[bi]['cellpos'].push([_w+bx, _h+by, _text]);
                    }
                }
            }

            blocks[bi]['x'] = bx;
            blocks[bi]['y'] = by;
        }

        var itemContainer = this.view.selectAll("g.itemContainer")
            .data(Object.values(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);

        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.cellpos)
            .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.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<sh; _h++){
            for(var _w=0; _w<sw; _w++){
                let line_index = data['solution']['map'][_h][_w];
                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]);

        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(){

    const board = new ADC2019BoardViewer("#board-container");

    hash = location.hash.replace("#", "");
    params = hash.split('/');

    $.ajax({
        type: "POST",
        dataType: "json",
        url: "/api/view/solution",
        data: JSON.stringify({
            "problem": params[0],
            "solution": params[1]
        }),
        contentType: 'application/json'
    }).done((d) => {
        board.draw(d);
    });

});
+3 −3
Changes for static/js/adc2019.js: 3 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ class StatusView {
            _this.container.find('.solution-detail-row 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 = "/viewer/solution#" + problem_name + "/" + solution_id;
                var viewer_url = "/view/solution#" + problem_name + "/" + solution_id;
                window.open(viewer_url, "_blank");
            })
        });
@@ -59,7 +59,7 @@ class StatusView {
                show: true
            });
            _this.container.find('#solver-processing-modal').on('hidden.bs.modal', function(e){
                console.log(e);
                // console.log(e);
                _this.show_problem();
            })

@@ -78,7 +78,7 @@ class StatusView {
            _this.container.find('#request-status-container').html(d);

            status = $(d).find('#request-status-value').text();
            console.log(status);
            // console.log(status);
            if(status == 'done'){
                _this.request_refresh_timer = null;
            }else{
+49 −0
Changes for templates/view-solution.html: 49 added lines, 0 removed lines.
Original line number Diff line number Diff line
<!DOCTYPE html>
<html>
<head>
    <title>ADC2019 Solver System</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/adc2019.css">
    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/js/bootstrap.bundle.min.js"></script>
    <script src="/static/js/d3.min.js"></script>
    <script src="/static/js/adc2019-viewer.js"></script>

    <style>
.axis path {
    display: none;
}
.axis line {
    stroke-opacity: 0.3;
    shape-rendering: crispEdges;
}
input[type="range"] {
    right: 0;
    top: 0;
    position: absolute;
}
svg{
    width: 100%;
    height: 100vh;
    display: block;
}
</style>
</head>
<body>

<div id="wrapper">
        
    <div id="control-panel-wrapper" class="container-fluid">

        <div id="board-container"></div>

    </div>


</div>

</body>
</html>
Loading