Adding Sankey for UDG

This commit is contained in:
Peter Morton 2023-06-26 22:03:41 -05:00
parent 1124aa1b64
commit 689554d78a
6 changed files with 1311 additions and 19 deletions

901
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
}
]
}

View File

@ -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>

View File

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

View 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>