Added filter and moved sample data to API
This commit is contained in:
parent
35364ee25e
commit
89fe29c162
@ -1,349 +1,185 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const multiSelected = ref(['Channel', 'Sub-Channel', 'Queue', 'Outcome'])
|
||||
|
||||
const errorMessage = ref(null);
|
||||
|
||||
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);
|
||||
fetchData()
|
||||
});
|
||||
|
||||
function fetchData() {
|
||||
//clear errors
|
||||
errorMessage.value = null;
|
||||
|
||||
fetch(
|
||||
`${import.meta.env.VITE_EO_SERVICES_URL}/interactions-flow?filter=${multiSelected.value}`,
|
||||
{
|
||||
credentials: "include", // fetch won't send cookies unless you set credentials
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
// check for error response
|
||||
if (!response.ok) {
|
||||
// get error message from body or default to response statusText
|
||||
const error = response.data || response.statusText;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
response.json().then((data) => {
|
||||
generateSankey(data);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
errorMessage.value = error;
|
||||
});
|
||||
}
|
||||
|
||||
function generateSankey(data) {
|
||||
|
||||
// filter data
|
||||
// const filteredData
|
||||
|
||||
if (data != null) {
|
||||
// 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"]
|
||||
|
||||
// clear the old svg
|
||||
d3.select("#chart").select("svg").remove()
|
||||
|
||||
// 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><select v-model="multiSelected" multiple style="width:200px; height:90px">
|
||||
<option disabled value="">Please select nodes</option>
|
||||
<option>Channel</option>
|
||||
<option>Sub-Channel</option>
|
||||
<option>Queue</option>
|
||||
<option>Outcome</option>
|
||||
</select>
|
||||
|
||||
<button @click="fetchData">Search Contacts</button>
|
||||
</div>
|
||||
|
||||
<div id="chart"></div>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user