fdg-img 0.4.2

A simple SVG renderer for fdg
Documentation
<script>
import Title from './components/Title.vue'
import ViewSwitcher from './components/ViewSwitcher.vue'

import init, { generate_svg } from 'fdg-img'

export default {
  name: 'App',

  components: {
    Title,
    ViewSwitcher,
  },

  data() {
    return {
      addNodeText: '',
      addEdgeSourceText: '',
      addEdgeTargetText: '',
      error: null,
      svg: '',
      isView: false,
      graph: {
        graph: {
          nodes: {
            B: {},
            A: {},
            C: {}
          },
          edges: [
            {
              source: 'A',
              target: 'B'
            },
            {
              source: 'B',
              target: 'C'
            },
            {
              source: 'C',
              target: 'A'
            }
          ],
        }
      },
      settings: {
        iterations: 2000,
        dt: 0.035,
        showText: true,
        textSize: 20,
        textColor: {
          r: 248,
          g: 250,
          b: 252,
          a: 1.0
        },
        backgroundColor: {
          r: 0,
          g: 0,
          b: 0,
          a: 0.0
        },
        nodeColor: {
          r: 30,
          g: 41,
          b: 59,
          a: 1.0
        },
        nodeSize: 10,
        edgeColor: {
          r: 100,
          g: 116,
          b: 139,
          a: 1.0
        },
        edgeSize: 5,
      }
    }
  },

  async mounted() {
    await init();

    // this.graph = await fetch('https://raw.githubusercontent.com/jsongraph/json-graph-specification/master/examples/les_miserables.json')
    //   .then(response => response.json());
  },

  methods: {
    setView(isView) {
      this.isView = isView;
    },
    
    async generateImage() {
      this.error = null;

      try {
        this.svg = generate_svg(this.graph, this.settings);
        document.getElementById('svgContainer').innerHTML = this.svg;
      } catch (error) {
        this.error = error;
      }

      document.getElementById('svgContainer').children[0].setAttribute('width', '100%');
    },

    removeNode(node) {
      delete this.graph.graph.nodes[node];
    },

    removeEdge(index) {
      this.graph.graph.edges.splice(index, 1);
    },

    addNode(event) {
      if (event != null && event.key != 'Enter') {
        return;
      }

      let name = this.addNodeText;

      if (name == '') {
        return;
      }

      if (this.graph.graph.nodes[name] == null || this.graph.graph.nodes[name] == undefined) {
        this.graph.graph.nodes[name] = {}
      }

      this.addNodeText = '';
    },

    addEdge() {
      let source = this.addEdgeSourceText;
      let target = this.addEdgeTargetText;

      if (source == null || target == null) {
        return;
      }

      let obj = {
        source: source,
        target: target,
      };

      if (!this.graph.graph.edges.includes(obj)) {
        this.graph.graph.edges.push(obj);

        this.addEdgeSourceText = '';
        this.addEdgeTargetText = '';
      }
    }
  }
}
</script>

<template>
  <div class="bg-slate-300 py-6 min-h-screen">
    <div class="w-5/6 md:w-1/2 mx-auto space-y-4">
      <Title class="c-block" />
      <div v-if="error" class="e-block">
        <p class="font-bold italic text-xl">{{ error }}</p>
      </div>
      <div class="bg-slate-700 text-slate-50 rounded-md shadow-lg">
        <ViewSwitcher v-on:setView="setView" />
        <div class="p-2">
          <div v-if="!isView" class="space-y-3 mx-auto text-center">
            <h3 class="italic text-2xl">Nodes</h3>
            <div class="bg-slate-800 rounded-md shadow-lg w-full md:w-2/3 mx-auto">
              <div v-for="(value, key) in graph.graph.nodes" class="p-2 space-y-1" :key="key">
                <div class="bg-slate-700 px-2 py-1 pl-3 rounded-md shadow-lg flow-root content-center">
                  <p class="float-left font-bold my-auto">{{ key }}</p>
                  <button v-on:click="removeNode(key)" type="button" class="float-right rounded-md p-2 inline-flex items-center justify-center rounded-full hover:bg-slate-600">
                    <span class="sr-only">Close</span>
                    <svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                  </button>
                </div>
              </div>
            </div>
            <div class="bg-slate-800 border-4 border-solid border-slate-800 rounded-md shadow-lg w-full md:w-2/3 mx-auto flex">
              <input type="text" v-model="addNodeText" v-on:keypress="addNode" class="rounded-tl-md rounded-bl-md flex-grow bg-slate-800 px-2 py-1"/>
              <button v-on:click="addNode()" type="button" class="rounded-tr-md rounded-br-md p-2 inline-flex items-center justify-center bg-slate-700 hover:bg-slate-600 font-semibold">Add</button>
            </div>

            <h3 class="italic text-2xl">Edges</h3>
            <div class="bg-slate-800 rounded-md shadow-lg w-full md:w-2/3 mx-auto">
              <div v-for="(edge, index) in graph.graph.edges" class="p-2 space-y-1" :key="edge">
                <div class="bg-slate-700 px-2 py-1 pl-3 rounded-md shadow-lg flow-root content-center">
                  <p class="float-left font-bold my-auto">Source: "{{ edge.source }}", Target: "{{ edge.target }}"</p>
                  <button v-on:click="removeEdge(index)" type="button" class="float-right rounded-md p-2 inline-flex items-center justify-center rounded-full hover:bg-slate-600">
                    <span class="sr-only">Close</span>
                    <svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                  </button>
                </div>
              </div>
            </div>
            <div class="bg-slate-800 border-4 border-solid border-slate-800 rounded-md shadow-lg w-full md:w-2/3 mx-auto flex">
              <div class="flex-grow grid grid-cols-4">
                <p class="bg-slate-700 align-middle">Source:</p>
                <input type="text" v-model="addEdgeSourceText" class="rounded-tl-md rounded-bl-md bg-slate-800 px-2 py-1"/>
                <p class="bg-slate-700 align-middle">Target:</p>
                <input type="text" v-model="addEdgeTargetText" class="rounded-tl-md rounded-bl-md bg-slate-800 px-2 py-1"/>
              </div>
              <button v-on:click="addEdge()" type="button" class="rounded-tr-md rounded-br-md p-2 inline-flex items-center justify-center bg-slate-700 hover:bg-slate-600 font-semibold">Add</button>
            </div>
          </div>
          <div v-else>
            <pre class="font-monospace rounded-md bg-slate-800 p-2">{{ JSON.stringify(graph, null, 2) }}</pre>
          </div>
        </div>
        <div class="flow-root pr-2 pb-2">
          <button v-if="!svg" class="float-right px-2 py-1 rounded-md shadow-lg bg-slate-600 hover:bg-slate-500" v-on:click="generateImage">Export</button>
          <button v-else class="float-right px-2 py-1 rounded-md shadow-lg bg-slate-600 hover:bg-slate-500" v-on:click="generateImage">Update</button>
        </div>
      </div>
      <div v-if="svg != '' || svg != null" class="c-block justify-items-center flex">
        <div class="mx-auto object-contain" id="svgContainer"></div>
      </div>
    </div>
  </div>
</template>