Adding Sankey for UDG
This commit is contained in:
parent
1124aa1b64
commit
689554d78a
901
package-lock.json
generated
901
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,10 +9,14 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3": "^7.8.5",
|
||||
"d3-sankey": "^0.12.3",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.2.37",
|
||||
"vue-router": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@vitejs/plugin-vue": "^3.1.0",
|
||||
"vite": "^3.1.0"
|
||||
}
|
||||
|
||||
75
public/data.json
Normal file
75
public/data.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Email",
|
||||
"category": "Channel"
|
||||
},
|
||||
{
|
||||
"name": "Messaging",
|
||||
"category": "Channel"
|
||||
},
|
||||
{
|
||||
"name": "Legacy Live Chat",
|
||||
"category": "Channel"
|
||||
},
|
||||
{
|
||||
"name": "Live Chat",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Facebook Messenger",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Twitter DM",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "WhatsApp",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Other",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Default",
|
||||
"category": "Queue"
|
||||
},
|
||||
{
|
||||
"name": "General Enquires",
|
||||
"category": "Queue"
|
||||
},
|
||||
{
|
||||
"name": "Complaints",
|
||||
"category": "Queue"
|
||||
},
|
||||
{
|
||||
"name": "Case Closed",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "Case Updated",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "Completed",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "Escalated to Manager",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "No Need for response",
|
||||
"category": "Outcome"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"source": "Email",
|
||||
"target": "Default",
|
||||
"value": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<!-- <router-link to="/">Home</router-link>
|
||||
<router-link to="/about">About</router-link>
|
||||
<router-link to="/referenceId">Reference ID Tracker</router-link> -->
|
||||
<router-link to="/">Home</router-link>
|
||||
<router-link to="/about">About</router-link>
|
||||
<router-link to="/referenceId">Reference ID Tracker</router-link>
|
||||
<router-link to="/interactionsSankey">Interaction Sankey</router-link>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
@ -1,6 +1,7 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
import ReferenceID from "../views/ReferenceID.vue";
|
||||
import InteractionsSankey from "../views/InteractionsSankey.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@ -18,6 +19,11 @@ const routes = [
|
||||
import(/* webpackChunkName: "about" */ "../views/About.vue"),
|
||||
},
|
||||
{ path: "/referenceId", name: "referenceId", component: ReferenceID },
|
||||
{
|
||||
path: "/interactionsSankey",
|
||||
name: "interactionsSankey",
|
||||
component: InteractionsSankey,
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
337
src/views/InteractionsSankey.vue
Normal file
337
src/views/InteractionsSankey.vue
Normal file
@ -0,0 +1,337 @@
|
||||
<script setup>
|
||||
import * as d3 from 'd3'
|
||||
import { sankey, sankeyLinkHorizontal, sankeyLeft} from 'd3-sankey';
|
||||
import { onMounted } from 'vue'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
|
||||
const data = {
|
||||
"nodes": [
|
||||
{
|
||||
"name": "Email",
|
||||
"category": "Channel"
|
||||
},
|
||||
{
|
||||
"name": "Messaging",
|
||||
"category": "Channel"
|
||||
},
|
||||
{
|
||||
"name": "Legacy Live Chat",
|
||||
"category": "Channel"
|
||||
},
|
||||
{
|
||||
"name": "Live Chat",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Facebook Messenger",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Twitter DM",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "WhatsApp",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Other",
|
||||
"category": "Sub Channel"
|
||||
},
|
||||
{
|
||||
"name": "Default",
|
||||
"category": "Queue"
|
||||
},
|
||||
{
|
||||
"name": "General Enquires",
|
||||
"category": "Queue"
|
||||
},
|
||||
{
|
||||
"name": "Complaints",
|
||||
"category": "Queue"
|
||||
},
|
||||
{
|
||||
"name": "Case Closed",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "Case Updated",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "Completed",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "Escalated to Manager",
|
||||
"category": "Outcome"
|
||||
},
|
||||
{
|
||||
"name": "No need for response",
|
||||
"category": "Outcome"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"source": "Email",
|
||||
"target": "Default",
|
||||
"value": 342
|
||||
},
|
||||
{
|
||||
"source": "Messaging",
|
||||
"target": "Live Chat",
|
||||
"value": 232
|
||||
},
|
||||
{
|
||||
"source": "Messaging",
|
||||
"target": "Facebook Messenger",
|
||||
"value": 623
|
||||
},
|
||||
{
|
||||
"source": "Messaging",
|
||||
"target": "Twitter DM",
|
||||
"value": 434
|
||||
},
|
||||
{
|
||||
"source": "Messaging",
|
||||
"target": "WhatsApp",
|
||||
"value": 1243
|
||||
},
|
||||
{
|
||||
"source": "Messaging",
|
||||
"target": "Other",
|
||||
"value": 150
|
||||
},
|
||||
{
|
||||
"source": "Live Chat",
|
||||
"target": "Default",
|
||||
"value": 132
|
||||
},
|
||||
{
|
||||
"source": "Live Chat",
|
||||
"target": "Complaints",
|
||||
"value": 90
|
||||
},
|
||||
{
|
||||
"source": "Live Chat",
|
||||
"target": "General Enquires",
|
||||
"value": 42
|
||||
},
|
||||
{
|
||||
"source": "WhatsApp",
|
||||
"target": "Default",
|
||||
"value": 343
|
||||
},
|
||||
{
|
||||
"source": "WhatsApp",
|
||||
"target": "Complaints",
|
||||
"value": 300
|
||||
},
|
||||
{
|
||||
"source": "WhatsApp",
|
||||
"target": "General Enquires",
|
||||
"value": 523
|
||||
},
|
||||
{
|
||||
"source": "Facebook Messenger",
|
||||
"target": "Default",
|
||||
"value": 143
|
||||
},
|
||||
{
|
||||
"source": "Facebook Messenger",
|
||||
"target": "Complaints",
|
||||
"value": 200
|
||||
},
|
||||
{
|
||||
"source": "Facebook Messenger",
|
||||
"target": "General Enquires",
|
||||
"value": 323
|
||||
},
|
||||
{
|
||||
"source": "Twitter DM",
|
||||
"target": "Default",
|
||||
"value": 143
|
||||
},
|
||||
{
|
||||
"source": "Twitter DM",
|
||||
"target": "Complaints",
|
||||
"value": 50
|
||||
},
|
||||
{
|
||||
"source": "Twitter DM",
|
||||
"target": "General Enquires",
|
||||
"value": 223
|
||||
},
|
||||
{
|
||||
"source": "General Enquires",
|
||||
"target": "Case Closed",
|
||||
"value": 421
|
||||
},
|
||||
{
|
||||
"source": "General Enquires",
|
||||
"target": "Completed",
|
||||
"value": 612
|
||||
},
|
||||
{
|
||||
"source": "General Enquires",
|
||||
"target": "Escalated to Manager",
|
||||
"value": 23
|
||||
},
|
||||
{
|
||||
"source": "General Enquires",
|
||||
"target": "No need for response",
|
||||
"value": 241
|
||||
},
|
||||
{
|
||||
"source": "Complaints",
|
||||
"target": "Case Closed",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"source": "Complaints",
|
||||
"target": "Completed",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"source": "Complaints",
|
||||
"target": "Escalated to Manager",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"source": "Complaints",
|
||||
"target": "No need for response",
|
||||
"value": 41
|
||||
},
|
||||
{
|
||||
"source": "Default",
|
||||
"target": "Completed",
|
||||
"value": 41
|
||||
},
|
||||
{
|
||||
"source": "Default",
|
||||
"target": "Case Updated",
|
||||
"value": 410
|
||||
},
|
||||
{
|
||||
"source": "General Enquires",
|
||||
"target": "Case Updated",
|
||||
"value": 50
|
||||
},
|
||||
{
|
||||
"source": "Complaints",
|
||||
"target": "Case Updated",
|
||||
"value": 410
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Specify the dimensions of the chart.
|
||||
const width = 928;
|
||||
const height = 600;
|
||||
const format = d3.format(",.0f");
|
||||
|
||||
const linkColor = "source-target" //
|
||||
// ["static", "#aaa"],
|
||||
// ["source-target", "source-target"],
|
||||
// ["source", "source"],
|
||||
// ["target", "target"]
|
||||
|
||||
// Create a SVG container.
|
||||
const svg = d3
|
||||
.select("#chart").append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("viewBox", [0, 0, width, height])
|
||||
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");
|
||||
|
||||
// Constructs and configures a Sankey generator.
|
||||
const sankeyGen = sankey()
|
||||
.nodeId(d => d.name)
|
||||
.nodeAlign(sankeyLeft) // d3.sankeyLeft, etc.
|
||||
.nodeWidth(15)
|
||||
.nodePadding(10)
|
||||
.extent([[1, 5], [width - 1, height - 5]]);
|
||||
|
||||
|
||||
// Applies it to the data. We make a copy of the nodes and links objects
|
||||
// so as to avoid mutating the original.
|
||||
const {nodes, links} = sankeyGen({
|
||||
nodes: data.nodes.map(d => Object.assign({}, d)),
|
||||
links: data.links.map(d => Object.assign({}, d))
|
||||
});
|
||||
|
||||
// Defines a color scale.
|
||||
const color = d3.scaleOrdinal(d3.schemeCategory10);
|
||||
|
||||
// Creates the rects that represent the nodes.
|
||||
const rect = svg.append("g")
|
||||
.attr("stroke", "#000")
|
||||
.selectAll()
|
||||
.data(nodes)
|
||||
.join("rect")
|
||||
.attr("x", d => d.x0)
|
||||
.attr("y", d => d.y0)
|
||||
.attr("height", d => d.y1 - d.y0)
|
||||
.attr("width", d => d.x1 - d.x0)
|
||||
.attr("fill", d => color(d.category));
|
||||
|
||||
// Adds a title on the nodes.
|
||||
rect.append("title")
|
||||
.text(d => `${d.name}\n${format(d.value)} TWh`);
|
||||
|
||||
// Creates the paths that represent the links.
|
||||
const link = svg.append("g")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke-opacity", 0.5)
|
||||
.selectAll()
|
||||
.data(links)
|
||||
.join("g")
|
||||
.style("mix-blend-mode", "multiply");
|
||||
|
||||
// Creates a gradient, if necessary, for the source-target color option.
|
||||
if (linkColor === "source-target") {
|
||||
const gradient = link.append("linearGradient")
|
||||
.attr('id', (d) => (d.uid = uuidv4()))
|
||||
.attr("gradientUnits", "userSpaceOnUse")
|
||||
.attr("x1", d => d.source.x1)
|
||||
.attr("x2", d => d.target.x0);
|
||||
gradient.append("stop")
|
||||
.attr("offset", "0%")
|
||||
.attr("stop-color", d => color(d.source.category));
|
||||
gradient.append("stop")
|
||||
.attr("offset", "100%")
|
||||
.attr("stop-color", d => color(d.target.category));
|
||||
}
|
||||
|
||||
link.append("path")
|
||||
.attr("d", sankeyLinkHorizontal())
|
||||
.attr("stroke", linkColor === "source-target" ? (d) => `url(#${d.uid})`
|
||||
: linkColor === "source" ? (d) => color(d.source.category)
|
||||
: linkColor === "target" ? (d) => color(d.target.category)
|
||||
: linkColor)
|
||||
.attr("stroke-width", d => Math.max(1, d.width));
|
||||
|
||||
link.append("title")
|
||||
.text(d => `${d.source.name} → ${d.target.name}\n${format(d.value)} TWh`);
|
||||
|
||||
// Adds labels on the nodes.
|
||||
svg.append("g")
|
||||
.selectAll()
|
||||
.data(nodes)
|
||||
.join("text")
|
||||
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
|
||||
.attr("y", d => (d.y1 + d.y0) / 2)
|
||||
.attr("dy", "0.35em")
|
||||
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
|
||||
.text(d => d.name);
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div id="chart"></div>
|
||||
</template>
|
||||
<style scoped>
|
||||
svg {
|
||||
padding-inline: max(2rem, calc(50% - 24rem));
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user