Infinite extent for field "y1": [Infinity, -Infinity]

Here is the code that I'm using for create sankey diagram:

{
$schema: https://vega.github.io/schema/vega/v3.0.json
data: [
{
// query ES based on the currently selected time range and filter string
name: rawData
url: {
%context%: true
%timefield%: @timestamp
index: instant_account_dashboard
body: {
size: 0
aggs: {
table: {
composite: {
size: 10000
sources: [
{
stk1: {
terms: {field: "api_event.VERIFY_PHONE"}
}
}
{
stk2: {
terms: {field: "api_event.VERIFY_OTP2"}
}
}
{
stk3: {
terms: {field: "api_event.JORNEY_ID_HISTORY"}
}
}
{
stk4: {
terms: {field: "api_event.VALIDATE_INSTANT_ACC_INFO"}
}
}
]
}
}
}
}
}
// From the result, take just the data we are interested in
format: {property: "aggregations.table.buckets"}
// Convert key.stk1 -> stk1 for simpler access below
transform: [
{type: "formula", expr: "datum.key.stk1", as: "stk1"}
{type: "formula", expr: "datum.key.stk2", as: "stk2"}
{type: "formula", expr: "datum.key.stk3", as: "stk3"}
{type: "formula", expr: "datum.key.stk4", as: "stk4"}
{type: "formula", expr: "datum.doc_count", as: "size"}
]
}
{
name: nodes
source: rawData
transform: [
// when a country is selected, filter out unrelated data
{
type: filter
expr: !groupSelector || groupSelector.stk1 == datum.stk1 || groupSelector.stk2 == datum.stk2 || groupSelector.stk3 == datum.stk3 || groupSelector.stk4 == datum.stk4
}
// Set new key for later lookups - identifies each node
{type: "formula", expr: "datum.stk1+datum.stk2+datum.stk3+datum.stk4", as: "key"}
// instead of each table row, create two new rows,
// one for the source (stack=stk1) and one for destination node (stack=stk2).
// The country code stored in stk1 and stk2 fields is placed into grpId field.
{
type: fold
fields: ["stk1", "stk2","stk3","stk4"]
as: ["stack", "grpId"]
}
// Create a sortkey, different for stk1 and stk2 stacks.
// Space separator ensures proper sort order in some corner cases.
{
type: formula
expr: datum.stack == 'stk1' ? datum.stk1+' '+datum.stk2+' '+datum.stk3+' '+datum.stk4:datum.stk4+' '+datum.stk3+ ' '+ datum.stk2+' '+datum.stk1
as: sortField
}
// Calculate y0 and y1 positions for stacking nodes one on top of the other,
// independently for each stack, and ensuring they are in the proper order,
// alphabetical from the top (reversed on the y axis)
{
type: stack
groupby: ["stack"]
sort: {field: "sortField", order: "descending"}
field: size
}
// calculate vertical center point for each node, used to draw edges
{type: "formula", expr: "(datum.y0+datum.y1)/2", as: "yc"}
]
}
{
name: groups
source: nodes
transform: [
// combine all nodes into country groups, summing up the doc counts
{
type: aggregate
groupby: ["stack", "grpId"]
fields: ["size"]
ops: ["sum"]
as: ["total"]
}
// re-calculate the stacking y0,y1 values
{
type: stack
groupby: ["stack"]
sort: {field: "grpId", order: "descending"}
field: total
}
// project y0 and y1 values to screen coordinates
// doing it once here instead of doing it several times in marks
{type: "formula", expr: "scale('y', datum.y0)", as: "scaledY0"}
{type: "formula", expr: "scale('y', datum.y1)", as: "scaledY1"}
// boolean flag if the label should be on the right of the stack
{type: "formula", expr: "datum.stack == 'stk3'", as: "rightLabel"}
{type: "formula", expr: "datum.stack == 'stk1'", as: "rightLabel"}
// Calculate traffic percentage for this country using "y" scale
// domain upper bound, which represents the total traffic
{
type: formula
expr: datum.total/domain('y')[1]
as: percentage
}
]
}
{
// This is a temp lookup table with all the 'stk2' stack nodes
name: destinationNodes
source: nodes
transform: [
{type: "filter", expr: "datum.stack == 'stk2'"}
]
}
{
// This is a temp lookup table with all the 'stk3' stack nodes
name: destinationNodes2
source: nodes
transform: [
{type: "filter", expr: "datum.stack == 'stk3'"}
]
}
{
// This is a temp lookup table with all the 'stk2' stack nodes
name: destinationNodes3
source: nodes
transform: [
{type: "filter", expr: "datum.stack == 'stk3'"}
]
}
{
name: edges
source: nodes
transform: [
// we only want nodes from the left stack
{type: "filter", expr: "datum.stack == 'stk1'"}

    // find corresponding node from the right stack, keep it as "target"
    {
      type: lookup
      from: destinationNodes
      key: key
      fields: ["key"]
      as: ["target"]
    }
   
    // calculate SVG link path between stk1 and stk2 stacks for the node pair
    {
      type: linkpath
      orient: horizontal
      shape: diagonal
      sourceY: {expr: "scale('y', datum.yc)"}
      sourceX: {expr: "scale('x', 'stk1') + bandwidth('x')"}
      targetY: {expr: "scale('y', datum.target.yc)"}
      targetX: {expr: "scale('x', 'stk2')"}
    }
    
    // A little trick to calculate the thickness of the line.
    // The value needs to be the same as the hight of the node, but scaling
    // size to screen's height gives inversed value because screen's Y
    // coordinate goes from the top to the bottom, whereas the graph's Y=0
    // is at the bottom. So subtracting scaled doc count from screen height
    // (which is the "lower" bound of the "y" scale) gives us the right value
    {
      type: formula
      expr: range('y')[0]-scale('y', datum.size)
      as: strokeWidth
    }
    // Tooltip needs individual link's percentage of all traffic
    {
      type: formula
      expr: datum.size/domain('y')[1]
      as: percentage
    }
  ]
}
{
  name: edges2
  source: nodes
  transform: [
    // we only want nodes from the left stack
    {type: "filter", expr: "datum.stack == 'stk2'"}
   
    // find corresponding node from the right stack, keep it as "target"
    {
      type: lookup
      from: destinationNodes2
      key: key
      fields: ["key"]
      as: ["target"]
    }
    
    // calculate SVG link path between stk1 and stk2 stacks for the node pair
    {
      type: linkpath
      orient: horizontal
      shape: diagonal
      sourceY: {expr: "scale('y', datum.yc)"}
      sourceX: {expr: "scale('x', 'stk2') + bandwidth('x')"}
      targetY: {expr: "scale('y', datum.target.yc)"}
      targetX: {expr: "scale('x', 'stk3')"}
    }
    
    // A little trick to calculate the thickness of the line.
    // The value needs to be the same as the hight of the node, but scaling
    // size to screen's height gives inversed value because screen's Y
    // coordinate goes from the top to the bottom, whereas the graph's Y=0
    // is at the bottom. So subtracting scaled doc count from screen height
    // (which is the "lower" bound of the "y" scale) gives us the right value
    {
      type: formula
      expr: range('y')[0]-scale('y', datum.size)
      as: strokeWidth
    }
    // Tooltip needs individual link's percentage of all traffic
    {
      type: formula
      expr: datum.size/domain('y')[1]
      as: percentage
    }
  ]
}
{
  name: edges3
  source: nodes
  transform: [
    // we only want nodes from the left stack
    {type: "filter", expr: "datum.stack == 'stk3'"}
   
    // find corresponding node from the right stack, keep it as "target"
    {
      type: lookup
      from: destinationNodes3
      key: key
      fields: ["key"]
      as: ["target"]
    }
    
    // calculate SVG link path between stk1 and stk2 stacks for the node pair
    {
      type: linkpath
      orient: horizontal
      shape: diagonal
      sourceY: {expr: "scale('y', datum.yc)"}
      sourceX: {expr: "scale('x', 'stk3') + bandwidth('x')"}
      targetY: {expr: "scale('y', datum.target.yc)"}
      targetX: {expr: "scale('x', 'stk4')"}
    }
    
    // A little trick to calculate the thickness of the line.
    // The value needs to be the same as the hight of the node, but scaling
    // size to screen's height gives inversed value because screen's Y
    // coordinate goes from the top to the bottom, whereas the graph's Y=0
    // is at the bottom. So subtracting scaled doc count from screen height
    // (which is the "lower" bound of the "y" scale) gives us the right value
    {
      type: formula
      expr: range('y')[0]-scale('y', datum.size)
      as: strokeWidth
    }
    // Tooltip needs individual link's percentage of all traffic
    {
      type: formula
      expr: datum.size/domain('y')[1]
      as: percentage
    }
  ]
}

]

scales: [
{
// calculates horizontal stack positioning
name: x
type: band
range: width
domain: ["stk1", "stk2","stk3","stk4"]
paddingOuter: 0.05
paddingInner: 0.95
}
{
// this scale goes up as high as the highest y1 value of all nodes
name: y
type: linear
range: height
domain: {data: "nodes", field: "y1"}
}
{
// use rawData to ensure the colors stay the same when clicking.
name: color
type: ordinal
range: category
domain: {data: "rawData", field: "stk1"}
}
{
// this scale is used to map internal ids (stk1, stk2) to stack names
name: stackNames
type: ordinal
range: ["Source", "Destination","Destination2","Destination3"]
domain: ["stk1", "stk2","stk3","stk4"]
}
]
axes: [
{
// x axis should use custom label formatting to print proper stack names
orient: bottom
scale: x
encode: {
labels: {
update: {
text: {scale: "stackNames", field: "value"}
}
}
}
}
{orient: "left", scale: "y"}
]
marks: [
{
// draw the connecting line between stacks
type: path
name: edgeMark
from: {data: "edges"}
// this prevents some autosizing issues with large strokeWidth for paths
clip: true
encode: {
update: {
// By default use color of the left node, except when showing traffic
// from just one country, in which case use destination color.
stroke: [
{
test: groupSelector && groupSelector.stack=='stk1'
scale: color
field: stk2
}
{scale: "color", field: "stk1"}
]
strokeWidth: {field: "strokeWidth"}
path: {field: "path"}
// when showing all traffic, and hovering over a country,
// highlight the traffic from that country.
strokeOpacity: {
signal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 0.9 : 0.3
}
// Ensure that the hover-selected edges show on top
zindex: {
signal: !groupSelector && (groupHover.stk1 == datum.stk1 || groupHover.stk2 == datum.stk2) ? 1 : 0
}
// format tooltip string
tooltip: {
signal: datum.stk1 + ' → ' + datum.stk2 + ' ' + format(datum.size, ',.0f') + ' (' + format(datum.percentage, '.1%') + ')'
}
}
// Simple mouseover highlighting of a single line
hover: {
strokeOpacity: {value: 1}
}
}
}
{
// draw the connecting line between stacks
type: path
name: edgeMark2
from: {data: "edges2"}
// this prevents some autosizing issues with large strokeWidth for paths
clip: true
encode: {
update: {
// By default use color of the left node, except when showing traffic
// from just one country, in which case use destination color.
stroke: [
{
test: groupSelector && groupSelector.stack=='stk2'
scale: color
field: stk3
}
{scale: "color", field: "stk3"}
]
strokeWidth: {field: "strokeWidth"}
path: {field: "path"}
// when showing all traffic, and hovering over a country,
// highlight the traffic from that country.
strokeOpacity: {
signal: !groupSelector && (groupHover.stk2 == datum.stk2 || groupHover.stk3 == datum.stk3) ? 0.9 : 0.3
}
// Ensure that the hover-selected edges show on top
zindex: {
signal: !groupSelector && (groupHover.stk2 == datum.stk2 || groupHover.stk3 == datum.stk3) ? 1 : 0
}
// format tooltip string
tooltip: {
signal: datum.stk2 + ' → ' + datum.stk3 + ' ' + format(datum.size, ',.0f') + ' (' + format(datum.percentage, '.1%') + ')'
}
}
// Simple mouseover highlighting of a single line
hover: {
strokeOpacity: {value: 1}
}
}
}
{
// draw the connecting line between stacks
type: path
name: edgeMark3
from: {data: "edges3"}
// this prevents some autosizing issues with large strokeWidth for paths
clip: true
encode: {
update: {
// By default use color of the left node, except when showing traffic
// from just one country, in which case use destination color.
stroke: [
{
test: groupSelector && groupSelector.stack=='stk3'
scale: color
field: stk3
}
{scale: "color", field: "stk3"}
]
strokeWidth: {field: "strokeWidth"}
path: {field: "path"}
// when showing all traffic, and hovering over a country,
// highlight the traffic from that country.
strokeOpacity: {
signal: !groupSelector && (groupHover.stk3 == datum.stk3 || groupHover.stk4 == datum.stk4) ? 0.9 : 0.3
}
// Ensure that the hover-selected edges show on top
zindex: {
signal: !groupSelector && (groupHover.stk3 == datum.stk2 || groupHover.stk3 == datum.stk4) ? 1 : 0
}
// format tooltip string
tooltip: {
signal: datum.stk3 + ' → ' + datum.stk4 + ' ' + format(datum.size, ',.0f') + ' (' + format(datum.percentage, '.1%') + ')'
}
}
// Simple mouseover highlighting of a single line
hover: {
strokeOpacity: {value: 1}
}
}
}
{
// draw stack groups (countries)
type: rect
name: groupMark
from: {data: "groups"}
encode: {
enter: {
fill: {scale: "color", field: "grpId"}
width: {scale: "x", band: 1}
}
update: {
x: {scale: "x", field: "stack"}
y: {field: "scaledY0"}
y2: {field: "scaledY1"}
fillOpacity: {value: 0.6}
tooltip: {
signal: datum.grpId + ' ' + format(datum.total, ',.0f') + ' (' + format(datum.percentage, '.1%') + ')'
}
}
hover: {
fillOpacity: {value: 1}
}
}
}
{
// draw country code labels on the inner side of the stack
type: text
from: {data: "groups"}
// don't process events for the labels - otherwise line mouseover is unclean
interactive: false
encode: {
update: {
// depending on which stack it is, position x with some padding
x: {
signal: scale('x', datum.stack) + (datum.rightLabel ? bandwidth('x') + 8 : -8)
}
// middle of the group
yc: {signal: "(datum.scaledY0 + datum.scaledY1)/2"}
align: {signal: "datum.rightLabel ? 'left' : 'right'"}
baseline: {value: "middle"}
fontWeight: {value: "bold"}
// only show text label if the group's height is large enough
text: {signal: "abs(datum.scaledY0-datum.scaledY1) > 13 ? datum.grpId : ''"}
}
}
}
{
// Create a "show all" button. Shown only when a country is selected.
type: group
data: [
// We need to make the button show only when groupSelector signal is true.
// Each mark is drawn as many times as there are elements in the backing data.
// Which means that if values list is empty, it will not be drawn.
// Here I create a data source with one empty object, and filter that list
// based on the signal value. This can only be done in a group.
{
name: dataForShowAll
values: [{}]
transform: [{type: "filter", expr: "groupSelector"}]
}
]
// Set button size and positioning
encode: {
enter: {
xc: {signal: "width/2"}
y: {value: 30}
width: {value: 80}
height: {value: 30}
}
}
marks: [
{
// This group is shown as a button with rounded corners.
type: group
// mark name allows signal capturing
name: groupReset
// Only shows button if dataForShowAll has values.
from: {data: "dataForShowAll"}
encode: {
enter: {
cornerRadius: {value: 6}
fill: {value: "#f5f5f5"}
stroke: {value: "#c1c1c1"}
strokeWidth: {value: 2}
// use parent group's size
height: {
field: {group: "height"}
}
width: {
field: {group: "width"}
}
}
update: {
// groups are transparent by default
opacity: {value: 1}
}
hover: {
opacity: {value: 0.7}
}
}
marks: [
{
type: text
// if true, it will prevent clicking on the button when over text.
interactive: false
encode: {
enter: {
// center text in the paren group
xc: {
field: {group: "width"}
mult: 0.5
}
yc: {
field: {group: "height"}
mult: 0.5
offset: 2
}
align: {value: "center"}
baseline: {value: "middle"}
fontWeight: {value: "bold"}
text: {value: "Show All"}
}
}
}
]
}
]
}
]
signals: [
{
// used to highlight traffic to/from the same country
name: groupHover
value: {}
on: [
{
events: @groupMark:mouseover
update: "{stk1:datum.stack=='stk1' && datum.grpId, stk2:datum.stack=='stk2' && datum.grpId,stk3:datum.stack=='stk3' && datum.grpId, stk4:datum.stack=='stk4' && datum.grpId}"
}
{events: "mouseout", update: "{}"}
]
}
// used to filter only the data related to the selected country
{
name: groupSelector
value: false
on: [
{
// Clicking groupMark sets this signal to the filter values
events: @groupMark:click!
update: "{stack:datum.stack, stk1:datum.stack=='stk1' && datum.grpId, stk2:datum.stack=='stk2' && datum.grpId,stk3:datum.stack=='stk3' && datum.grpId, stk4:datum.stack=='stk4' && datum.grpId}"
}
{
// Clicking "show all" button, or double-clicking anywhere resets it
events: [
{type: "click", markname: "groupReset"}
{type: "dblclick"}
]
update: "false"
}
]
}
]
}

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.