목차

목차

개요

본 문서는 모니터링 대시보드에서 SVG를 다루기 위한 추가 개발에 고려된 기술적 내용을 담았다.

업데이트 요약

구현 상세

CSS3에서 지원하는 스펙 사항을 플러그인에서 활용하기 위해 스타일시트(CSS)를 동적으로 요청할 필요성이 있다. jQuery를 이용하는 아래 구문으로 CSS의 동적 로드가 가능하다.

$("<link/>", {
        rel: "stylesheet",
        type: "text/css",
        href: this.panel.assets.basePath + '/' + 'style.spin.css?' + this.createRandomId('', 10)
    }).appendTo("head");

CSS를 동적으로 로드해야 할 시에는 브라우저에 캐시로 남지 않도록 (만료 기간을 1회성으로 하기 위해) 랜덤 값 생성 함수를 같이 이용하여야 한다. 아래와 같은 랜덤 함수를 사용하면 된다. 랜덤 함수는 플러그인에서 상당히 많은 부분에 중요한 역할을 한다.

createRandomId(prefix, size) {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    for (var i = 0; i < size; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }

    return (prefix + text);
  }

SVG 벡터 이미지는 XML 형식을 따르고 있다. SVG 벡터 이미지의 원활한 제어를 위해서는 SnapSVG-JS와 jQuery의 적절한 병행 이용이 필요하다.

var els = [];
var svg = Snap("div." + ctrl.panel.canvasname).select("svg");
var gs = svg.selectAll('*');
gs.forEach(function(el) {
	var $xml = $(el.toString()),
	   $elmid = $xml.eq(0).attr("id"),
	   $elmHasCls = $xml.eq(0).hasClass("thing");
           // thing 클래스만 목록으로 반환.

	// 이미 ID를 가지고 있으며, 클래스가 thing 인것만 목록으로 넘김.
	if(typeof($elmid) != 'undefined' && $elmHasCls == true) {
	   els.push( { "id": $elmid, "xlocation": 100, "ylocation": 100, "size": 22 } );
	}
});

CSS3은 키프레임 설명자(KeyFrame-Descriptor)를 지원하여 애니메이션의 키프레임의 변화를 지정할 수 있다. 가령, 키프레임이 변형된 후 원형으로 돌아오는데 걸리는 시간을 지정하여 애니메이션 효과를 지정할 수 있다. 예제는 아래와 같다.

.rotate {
    -webkit-animation: rotation 2s infinite linear;
    -moz-animation: rotation 2s infinite linear;
    -o-animation: rotation 2s infinite linear;
    animation: rotation 2s infinite linear;
    transform-origin: 50% 50%;
    -webkit-transform-origin: 50% 50%;
    -moz-transform-origin: 50% 50%;
}

@-webkit-keyframes rotation {
    from {-webkit-transform: rotate(0deg);}
    to   {-webkit-transform: rotate(359deg);}
}
@-moz-keyframes rotation {
    from {-moz-transform: rotate(0deg);}
    to   {-moz-transform: rotate(359deg);}
}
@-o-keyframes rotation {
    from {-o-transform: rotate(0deg);}
    to   {-o-transform: rotate(359deg);}
}
@keyframes rotation {
    from {transform: rotate(0deg);}
    to   {transform: rotate(359deg);}
}

위 예제는 회전이 중앙 축을 기준으로 0도에서 359도 (약 360도) 회전하는데 걸리는 시간(원형 키프레임으로 돌아오는 시간)을 지정하여 회전 애니메이션 효과를 지정한다. 역방향 키프레임 효과는 목적 각도(deg of to)에 마이너스(-) 값을 주어서 구현이 가능하다. 브라우저 벤더를 식별하는 문자로 webkit, moz, o 등이 존재하는데, 벤더 식별 문자가 들어가야만 해석을 하는 브라우저가 있기 때문에 같이 넣어주어야 한다.

프리셋을 통해 조건에 따른 애니메이션이나 색상 효과를 지정할 수 있다. 패널 옵션에서 프리셋의 속성을 바꿔치기(override) 할 수 있도록 지원하여 사용자가 직접 지정할 수 있다.

{
    "gt":{"value":100,"style":{"fill":"red"},"animate":{"spinspeed":9}},
    "lt":{"value":1000,"style":{"fill":"yellow"},"animate":{"spinspeed":6}},
    "gte":{"value":10000,"style":{"fill":"green"},"animate":{"spinspeed":3}},
    "lte":{"value":100000,"style":{"fill":"white"},"animate":{"spinspeed":0}},
    "eq":{"value":1000000,"style":{"fill":"gray"},"animate":{"spinspeed":12}},
    "not":{"style":{"fill":"gray"}}
}

해당 속성을 바꿔치기(override)하는 것은 옵션 양식(editor.html)에서 입력을 지원하는 것으로도 가능하다. 또한 컨트롤러(ctrl.js)에서 override에 대한 더 상세한 조건을 코드로 지정할 수 있다. 아래는 override를 위해 사용자 값을 입력받는 양식의 예제이다.

<div class="section gf-form-group">
    <h5 class="section-heading">Preset override</h5>
        <div class="gf-form">
                <label class="gf-form-label width-4">std</label>
                <input type="text" class="input-small gf-form-input width-8" ng-model="sensor.std" ng-change="ctrl.render()" ng-model-onblur />
        </div>
        (... 생략 ...)
</div>

SVG 벡터 이미지의 좌표 속성은 사후결정의 특징이 있어 축 좌표를 지정하기 위해 사전에 중간 값을 뜻하는 50%를 수치로 지정해놓아도 정확한 축 좌표를 찾지 않는다. 즉 사후결정 방식에 맞게 객체 로드가 완료된 후 축 좌표를 계산하는 것이 필요하며 그 계산은 아래와 같이 가능하다.

// 중심 축좌표 계산
var coord = s.getBBox();
var cx = coord.x + (coord.width / 2);
var cy = coord.y + (coord.height / 2);

// 중심 축으로 회전
var $node = $(s.node);
$node.addClass("rotate");
$node.css({
	"transform-origin": cx + "px " + cy + "px",
	"-webkit-transform-origin": cx + "px " + cy + "px",
	"-moz-transform-origin": cx + "px " + cy + "px"
});

랜덤 값 생성 함수를 이용하여 다중 패널을 구현할 수 있다. 플러그인의 객체 생성자(플러그인 클래스의 생성자)에서 랜덤 값을 이용하여 캔버스 이름(SVG가 표현될 바탕이 될 객체의 이름)을 지정한다. 모듈 템플릿은 생성된 캔버스 이름을 가지고 객체를 생성한다.

export class [blind] extends [blind]  {
  constructor($scope, $injector) {
    super($scope, $injector);
    _.defaults(this.panel, panelDefaults);
    (... 생략 ....)

    // 캔버스 코드 생성
    this.panel.canvasname = "canvas_" + this.createRandomId('', 10);
  }

이후 진행되는 과정은 앞서 명시된 SVG 벡터 객체 추적과 관련이 있으니 추가 개발 시 참고하면 된다.

해당 플러그인의 컴파일(빌드) 명세는 grunt 빌드 환경에 의해 관리된다. 정상적인 빌드를 위해서는 각 프로젝트 내 디렉토리의 역할 및 작업을 지정할 필요가 있다. 아래는 빌드 시 실행될 작업(task)를 등록하는 예시이다.

(... 생략 ...)
assets: {
        cwd: 'src',
        expand: true,
        src: ['**/assets/*'],
        dest: 'dist'
      },
      extern: {
        cwd: 'src',
        expand: true,
        src: ['**/extern/*'],
        dest: 'dist'
      }
}
(... 생략 ...)
    babel: {
      options: {
        sourceMap: true,
        presets: ['es2015'],
        plugins: ['transform-es2015-modules-systemjs', 'transform-es2015-for-of'],
      },
      dist: {
        files: [{
          cwd: 'src',
          expand: true,
          src: ['*.js'],
          dest: 'dist',
          ext: '.js'
        }]
      },
    },
    (... 생략 ...)

grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:pluginDef', 'copy:extern', 'copy:assets', 'babel']);

빌드는 프로젝트 디렉토리에서 ‘grunt’ 명령을 이용한다. 해당 명령을 이용하면 아래와 같은 결과를 얻을 수 있다. 이후 프로젝트 디렉토리 전체를 /var/lib/visualpanel/plugins 하위에 복사하면 설치는 완료된다. 아래 예제는 플러그인 디렉토리 내에서 빌드한 것이다.

root@compute:/var/lib/visualpanel/plugins/customspin-svgctrl-panel# grunt
Running "clean:0" (clean) task
>> 1 path cleaned.

(... 생략 ...)

Running "copy:extern" (copy) task
Created 1 directory, copied 9 files

Running "copy:assets" (copy) task
Created 2 directories, copied 219 files

Running "babel:dist" (babel) task

Done, without errors.

동적 대시보드 개발 명세문서 마침.