2022-05-24,   Do-soo, KIM

이전 포스팅에서 프로세스마이닝 기법 중 하나로 알파 알고리즘에 대해서 보셨을 겁니다.

알파 알고리즘을 통해 Petri net이라는 다이어그램을 도출했었죠?

기억이 안 나신다면, 아래의 이전 포스팅 내용을 확인해 보세요.

[프로세스 마이닝 - 알파 알고리즘 적용] (클릭하시면 해당 포스팅으로 이동합니다.)

이번에 진행하는 프로젝트 중 Perti Net을 웹 화면에 그려야 해서 관련 오픈 소스 라이브러리를 조사해 보던 중 괜찮은 라이브러리가 있어서 이번 포스팅에서 소개할까 합니다.

바로 Joint JS라는 라이브러리인데, 대화형 SVG 기반 다이어그램 및 시각적 응용 프로그램을 구축하기 위한 유연한 Javascript 라이브러리입니다.

정적 다이어그램은 물론이고 완전한 대화형 다이어그램 도구 및 응용 프로그램 빌더를 만드는 데 사용할 수 있습니다.

위에서 언급한 SVG(Scalable Vector Graphics)는 2차원 그래픽을 표현하기 위해 만들어진 XML파일 형식의 마크업 언어입니다.

Vector는 기본적인 도형, 패스 등 일러스트에서 작업하는 모든 것이 될 수 있는데, 일반적으로 웹 상 이미지 파일형식은 JPEG를 많이 사용합니다.

SVG에 대해서는 이 정도로만 설명하기로 하고 본론으로 돌아와 Joint JS에 대해서 살펴 보겠습니다.

이전 포스팅에서 도출했던 Petri Net은 다음과 같습니다.

image

이 Petri Net을 Joint JS를 사용하여 웹 화면에서 구현하는 과정을 이제부터 차근차근 해 보도록 하겠습니다.

1. 페이지 생성

먼저 웹 페이지를 하나 생성하고 Joint JS를 사용하기 위한 js 파일을 등록해 줍니다.

(js 파일은 이미 빌드가 되어 있는 상태라고 가정하고 진행하겠습니다.)

<div id="paper"></div>

<script src="https://cdn.jsdelivr.net/npm/fake-smile@1.0.1/smil.user.min.js"></script>
<script src="../../node_modules/jquery/dist/jquery.js"></script>
<script src="../../node_modules/lodash/lodash.js"></script>
<script src="../../node_modules/backbone/backbone.js"></script>
<script src="../../build/joint.js"></script>
<script>
    var graph = new joint.dia.Graph();
    new joint.dia.Paper({
        el: document.getElementById('paper'),
        width: 800,
        height: 450,
        gridSize: 10,
        defaultAnchor: { name: 'perpendicular' },
        defaultConnectionPoint: { name: 'boundary' },
        model: graph
    });
</script>

2. Transition 생성

위 Petri Net은, 7개의 Transition과 10개의 Place가 필요하군요.

그럼 먼저, Transition을 생성해 보겠습니다.

var transition_a = new joint.shapes.pn.Transition({
    position: { x: 150, y: 100 },
    attrs: {
        '.label': {
            'text': 'a',
            'fill': '#fe854f'
        },
        '.root': {
            'fill': '#9586fd',
            'stroke': '#9586fd'
        }
    }
});

이제 나머지 6개도 생성해 볼 건데, 위와 같이 생성하지 않아도 됩니다.

어차피 라벨과 위치 정보만 다르고 모든 속성은 동일하기 때문에 위에서 생성했던 Transition 객체를 복제한 다음 라벨과 위치 정보만 추가해 주면 됩니다.

var transition_b = transition_a.clone()
    .attr('.label/text', 'b')    
    .position(150, 300);

var transition_c = transition_a.clone()
    .attr('.label/text', 'c')
    .position(400, 50);

var transition_d = transition_a.clone()
    .attr('.label/text', 'd')
    .position(400, 150);

var transition_e = transition_a.clone()
    .attr('.label/text', 'e')
    .position(400, 250);

var transition_f = transition_a.clone()
    .attr('.label/text', 'f')
    .position(400, 350);

var transition_g = transition_a.clone()
    .attr('.label/text', 'g')
    .position(650, 200);

graph.addCell([transition_a, transition_b, transition_c, transition_d, transition_e, transition_f, transition_g]);  

3. Place 생성

Transition은 다 생성했으니 이제 Place도 생성해 보겠습니다.

Transition과 마찬가지로 1개를 생성하고 나머지 9개는 복제하는 방법으로 생성하면 됩니다.

var place_start = new joint.shapes.pn.Place({
    position: { x: 30, y: 200 },
    attrs: {
        '.label': {
            'text': 'start',
            'fill': '#7c68fc'
        },
        '.root': {
            'stroke': '#9586fd',
            'stroke-width': 3
        },
    }
});

var place_1 = place_start.clone()
    .attr('.label/text', '')
    .position(250, 50);

var place_2 = place_start.clone()
    .attr('.label/text', '')    
    .position(250, 150);
    
var place_3 = place_start.clone()
    .attr('.label/text', '')
    .position(250, 250);

var place_4 = place_start.clone()
    .attr('.label/text', '')
    .position(250, 350);

var place_5 = place_start.clone()
    .attr('.label/text', '')
    .position(500, 50);

var place_6 = place_start.clone()
    .attr('.label/text', '')
    .position(500, 150);

var place_7 = place_start.clone()
    .attr('.label/text', '')
    .position(500, 250);

var place_8 = place_start.clone()
    .attr('.label/text', '')
    .position(500, 350);

var place_end = place_start.clone()
    .attr('.label/text', 'end')
    .position(750, 200);

graph.addCell([place_start, place_1, place_2, place_3, place_4, place_5, place_6, place_7, place_8, place_end]);

이제 생성된 객체끼리 연결만 하면 되겠네요.

Link 객체를 이용해서 화살표를 생성하면 됩니다.

Link 객체는 연결의 source와 target 파라미터가 각각 다르므로, 함수로 만들어서 각각 호출하여 사용해 보려고 합니다.

function link(a, b) {
    return new joint.shapes.standard.Link({
        source: { id: a.id, selector: '.root' },
        target: { id: b.id, selector: '.root' },
        attrs: {
            line: {
                stroke: '#ffffff',
                sourceMarker: {
                },
                targetMarker: {
                    'type': 'path',
                    'stroke': '#ffffff',
                    'fill': '#ffffff',
                }
            }
        }
    });
}

이렇게 함수를 만들었구요.

호출만 하면 되겠죠?

graph.addCell([
    link(place_start, transition_a),
    link(place_start, transition_b),
    link(transition_a, place_1),
    link(transition_a, place_2),
    link(transition_b, place_3),
    link(transition_b, place_4),
    link(place_1, transition_c),
    link(place_2, transition_e),
    link(place_3, transition_d),
    link(place_4, transition_f),
    link(transition_c, place_5),
    link(transition_c, place_6),
    link(transition_d, place_6),
    link(transition_d, place_7),
    link(transition_e, place_7),
    link(transition_e, place_8),
    link(transition_f, place_5),
    link(transition_f, place_8),
    link(place_5, transition_g),
    link(place_6, transition_g),
    link(place_7, transition_g),
    link(place_8, transition_g),
    link(transition_g, place_end),
]);

자! 만들어진 Petri Net을 확인해 볼까요?

image

짜잔!! 비슷하게 만들어 졌나요?

그럼 이번 포스팅은 여기서 마치겠습니다.

References

https://svgontheweb.com/ko/
https://resources.jointjs.com/demos/pn

업데이트: