@ -191,6 +191,9 @@ function fillEchartsDataSet(myRs,myRsMetaData,currentChartModel,seriesDistinctCo
@@ -191,6 +191,9 @@ function fillEchartsDataSet(myRs,myRsMetaData,currentChartModel,seriesDistinctCo
case "echarts_generic" :
dataset = fillEchartsDataSetSimpleCopy ( myRs , myRsMetaData ) ;
break ;
case "echarts_sankey" :
dataset = myRs ;
break ;
default :
//dataset=fillEchartsDataSetSimpleCopy(myRs,myRsMetaData);
var seriesFldname = getChartElementPropertyValueFromModel ( currentChartModel , "1" , "stroke" ) ;
@ -1082,6 +1085,7 @@ renderSankey(myCommonChartProperties,mySvg,sNodes,sLinks);
@@ -1082,6 +1085,7 @@ renderSankey(myCommonChartProperties,mySvg,sNodes,sLinks);
function getSankeyNodes ( data , captionEmptyTarget )
{
//obsolet
var myNodes = [ ] ;
var distinctSource = [ ] ;
@ -1114,6 +1118,7 @@ return myNodes;
@@ -1114,6 +1118,7 @@ return myNodes;
function getSankeyLinks ( myNodes , data )
{
//obsolet
var myLinks = [ ] ;
var linkIndex = 0 ;
@ -1136,6 +1141,7 @@ return myLinks;
@@ -1136,6 +1141,7 @@ return myLinks;
}
function getSankeyNodeIndex ( myNodes , name )
{
//obsolet
var myIndex = 0 ;
for ( var i = 0 ; i < myNodes . length ; i ++ )
{
@ -1185,6 +1191,7 @@ function getMetadataOfVizTypeProperty(metaData,vizTypePropertyUniquename)
@@ -1185,6 +1191,7 @@ function getMetadataOfVizTypeProperty(metaData,vizTypePropertyUniquename)
}
function renderSankey ( myCommonChartProperties , svg , sNodes , sLinks )
{
//obsolet
// set the dimensions and margins of the graph
var margin = { top : 10 , right : 10 , bottom : 10 , left : 10 } ,
//var margin = { top: 0, right: 50, bottom: 0, left: 0 },
@ -2500,226 +2507,154 @@ function getEchartOptionsSankey(currentChartModel,myDataset)
@@ -2500,226 +2507,154 @@ function getEchartOptionsSankey(currentChartModel,myDataset)
var measure1Fldname = getChartElementPropertyValueFromModel ( currentChartModel , "1" , "measure1" ) ;
var dimension1Fldname = getChartElementPropertyValueFromModel ( currentChartModel , "1" , "dimension1" ) ;
var dimension2Fldname = getChartElementPropertyValueFromModel ( currentChartModel , "1" , "dimension2" ) ;
var titleText = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "caption" ) ;
var subtitleText = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "subtitle" ) ;
var scheme = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "schemeArray" ) ;
var myPalette = getPaletteArray ( scheme , "" ) ;
var captionEmptyNode = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "null_value_mask" ) ;
if ( captionEmptyNode == "" )
captionEmptyNode = "Leer" ;
/ * c o n s t s o u r c e D a t a = [
{ "rownr" : 1 , "pj_ba_str" : "2023" , "pj_ba_sem_von_str" : "WiSe 2022/2023" , "pj_ba_sem_bis_str" : "SoSe 2023" , "ba_abschluss_grp_str" : "Bachelor" , "ba_abschluesse_summe" : 1964 , "ma_sem_von_str" : "WiSe 2022/2023" , "ma_sem_bis_str" : "WiSe 2024/2025" , "ma_abschluss_grp_str" : "LA Master" , "ma_anfaenger_summe" : 2 , "ba_ma_ueberg_quote" : 0 } ,
{ "rownr" : 2 , "pj_ba_str" : "2023" , "pj_ba_sem_von_str" : "WiSe 2022/2023" , "pj_ba_sem_bis_str" : "SoSe 2023" , "ba_abschluss_grp_str" : "Bachelor" , "ba_abschluesse_summe" : 1964 , "ma_sem_von_str" : "WiSe 2022/2023" , "ma_sem_bis_str" : "WiSe 2024/2025" , "ma_abschluss_grp_str" : "Master" , "ma_anfaenger_summe" : 706 , "ba_ma_ueberg_quote" : 0.36 } ,
{ "rownr" : 3 , "pj_ba_str" : "2023" , "pj_ba_sem_von_str" : "WiSe 2022/2023" , "pj_ba_sem_bis_str" : "SoSe 2023" , "ba_abschluss_grp_str" : "Bachelor" , "ba_abschluesse_summe" : 1964 , "ma_sem_von_str" : "WiSe 2022/2023" , "ma_sem_bis_str" : "WiSe 2024/2025" , "ma_abschluss_grp_str" : "kein Master" , "ma_anfaenger_summe" : 1256 , "ba_ma_ueberg_quote" : 0.64 } ,
{ "rownr" : 4 , "pj_ba_str" : "2023" , "pj_ba_sem_von_str" : "WiSe 2022/2023" , "pj_ba_sem_bis_str" : "SoSe 2023" , "ba_abschluss_grp_str" : "LA Bachelor" , "ba_abschluesse_summe" : 1392 , "ma_sem_von_str" : "WiSe 2022/2023" , "ma_sem_bis_str" : "WiSe 2024/2025" , "ma_abschluss_grp_str" : "LA Master" , "ma_anfaenger_summe" : 1258 , "ba_ma_ueberg_quote" : 0.9 } ,
{ "rownr" : 5 , "pj_ba_str" : "2023" , "pj_ba_sem_von_str" : "WiSe 2022/2023" , "pj_ba_sem_bis_str" : "SoSe 2023" , "ba_abschluss_grp_str" : "LA Bachelor" , "ba_abschluesse_summe" : 1392 , "ma_sem_von_str" : "WiSe 2022/2023" , "ma_sem_bis_str" : "WiSe 2024/2025" , "ma_abschluss_grp_str" : "Master" , "ma_anfaenger_summe" : 21 , "ba_ma_ueberg_quote" : 0.02 } ,
{ "rownr" : 6 , "pj_ba_str" : "2023" , "pj_ba_sem_von_str" : "WiSe 2022/2023" , "pj_ba_sem_bis_str" : "SoSe 2023" , "ba_abschluss_grp_str" : "LA Bachelor" , "ba_abschluesse_summe" : 1392 , "ma_sem_von_str" : "WiSe 2022/2023" , "ma_sem_bis_str" : "WiSe 2024/2025" , "ma_abschluss_grp_str" : "kein Master" , "ma_anfaenger_summe" : 113 , "ba_ma_ueberg_quote" : 0.08 }
] ; * /
const sourceData = myDataset . source ;
// DEBUG: Zeige die Rohdaten an
console . log ( 'DATEN:' , sourceData ) ;
// Farbzuordnung (intensivere Töne für besseren Kontrast)
const colorMap = {
'Bachelor' : '#3a5a2c' ,
'LA Bachelor' : '#8a9e7a' ,
'Master' : '#001e30' ,
'LA Master' : '#5a7a90' ,
'kein Master' : '#c0c8d0'
} ;
var marginLeft = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "margin.left" ) ;
var marginRight = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "margin.right" ) ;
var marginTop = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "margin.top" ) ;
var marginBottom = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "margin.bottom" ) ;
// ✅ Erstelle ein Map für die Gesamtwerte der Quellknoten (ba_abschluesse_summe)
const sourceTotalMap = new Map ( ) ;
sourceData . forEach ( row => {
const sourceName = row . ba _abschluss _grp _str ;
const total = row . ba _abschluesse _summe ;
var valueLabelShow = ( getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "valueLabelShow" ) == "true" ) ? true : false ;
var valueLabelFontColor = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "valueLabelFontColor" ) ;
var valueLabelFontSize = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "valueLabelFontSize" ) ;
var valueLabelFontFamily = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "valueLabelFontFamily" ) ;
var position _valueLabel = getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "position_valueLabel" ) ;
if ( ! sourceTotalMap . has ( sourceName ) ) {
sourceTotalMap . set ( sourceName , total ) ;
} else if ( sourceTotalMap . get ( sourceName ) !== total ) {
console . warn ( ` ⚠️ Inkonsequenter Gesamtwert für Quellknoten " ${ sourceName } ": ${ total } vs ${ sourceTotalMap . get ( sourceName ) } ` ) ;
}
} ) ;
// DEBUG: Zeige die Quellgesamtwerte an
console . log ( 'Gesamtwerte der Quellknoten (ba_abschluesse_summe):' , Object . fromEntries ( sourceTotalMap ) ) ;
var dataNodeArray = new Array ( ) ;
dataNodeArray = getDistinctColumnValuesAsSankeyNodes ( myDataset , dimension1Fldname , dataNodeArray , captionEmptyNode ) ;
dataNodeArray = getDistinctColumnValuesAsSankeyNodes ( myDataset , dimension2Fldname , dataNodeArray , captionEmptyNode ) ;
// ✅ Generiere die Links mit Farbverlauf (Gradienten)
const links = sourceData . map ( row => {
const source = row . ba _abschluss _grp _str ;
const target = row . ma _abschluss _grp _str ;
const value = row . ma _anfaenger _summe ;
const sourceColor = colorMap [ source ] ;
const targetColor = colorMap [ target ] ;
var linkArray = new Array ( ) ;
return {
source ,
target ,
value ,
lineStyle : {
width : 2 ,
color : echartsSankeyCreateGradientColor ( sourceColor , targetColor ) ,
opacity : 0.3
}
myDataset . forEach ( row => {
var sourceName = row [ dimension1Fldname ] . toString ( ) ;
if ( sourceName == "" ) {
sourceName = captionEmptyNode ;
}
var targetName = row [ dimension2Fldname ] . toString ( ) ;
if ( targetName == "" ) {
targetName = captionEmptyNode ;
}
var linkValue = row [ measure1Fldname ] ;
var linkObj = { source : sourceName ,
target : targetName ,
value : linkValue
} ;
} ) ;
linkArray . push ( linkObj ) ;
// DEBUG: Zeige die generierten Links an
console . log ( 'GENERIERTE LINKS mit Gradienten:' , links ) ;
} ) ;
// ECharts Option
var option = {
title : {
text : 'Übergang von Bachelor zu Master' ,
text : titleText ,
left : 'center' ,
textStyle : { fontSize : 16 }
textStyle : { fontSize : "2em" }
} ,
tooltip : {
trigger : 'item' ,
triggerOn : 'mousemove' ,
formatter : function ( params ) {
if ( params . dataType === 'node' ) {
const nodeName = params . name ;
// Knotenfarbe als Tooltip-Farbe
const color = colorMap [ nodeName ] || '#ccc' ;
// Prüfe, ob der Knoten als Quelle oder Ziel vorkommt
const isSource = links . some ( link => link . source === nodeName ) ;
const isTarget = links . some ( link => link . target === nodeName ) ;
if ( isSource && ! isTarget ) {
// Nur Quelle (links): Name + ba_abschluesse_summe
const total = sourceTotalMap . get ( nodeName ) ;
return ` <span style="color: ${ color } ; font-weight:bold"> ${ nodeName } -Abschlüsse: ${ total } ` ;
} else if ( isTarget ) {
// Ziel (rechts): Name + summierte ankommende Werte
const incomingValue = links
. filter ( link => link . target === nodeName )
. reduce ( ( sum , link ) => sum + link . value , 0 ) ;
return ` <span style="color: ${ color } ; font-weight:bold"> ${ nodeName } -Studiengänge: ${ incomingValue } ` ;
} else {
return nodeName ;
}
} else {
// Link-Tooltip
return params . data . source + ' --> ' + params . data . target + ': ' + params . data . value ;
}
}
} ,
series : {
name : 'Bachelor-Master Übergänge' ,
if ( params . dataType === 'node' ) {
var nodeName = params . name ;
return nodeName + ': ' + params . value . toLocaleString ( ) ;
}
else
{ //link:
return params . data . source + ' --> ' + params . data . target + ': ' + params . data . value . toLocaleString ( ) ;
}
}
} ,
series : {
name : titleText ,
type : 'sankey' ,
layout : 'none' ,
nodeGap : 25 , // ← Erhöhter vertikaler Abstand
nodeWidth : 60 , // ← Breite der Knoten
layout : 'center' ,
//layoutIterations: 0, // Um automatische Sortierung zu deaktivieren
//nodeOrder: ['Bachelor', 'Kein Master BUW','Master'] ,//'LA Bachelor', 'kein Bachelor an UzK', 'Master', 'LA Master', 'kein Master an UzK'],
nodeAlign : 'left' ,
nodeGap : 25 , // Erhöhter vertikaler Abstand
nodeWidth : 60 , // Breite der Knoten
edgeLabel : { show : valueLabelShow ,
fontFamily : valueLabelFontFamily ,
color : valueLabelFontColor ,
fontSize : valueLabelFontSize ,
formatter : function ( params ) {
return params . value . toLocaleString ( )
}
} ,
emphasis : {
focus : 'adjacency'
disabled : true
//focus: 'self' //'adjacency'
} ,
data : [
{
name : 'Bachelor' ,
itemStyle : { color : colorMap [ 'Bachelor' ] } ,
label : { position : 'left' , show : true , fontSize : 12 , color : '#333' }
} ,
{
name : 'LA Bachelor' ,
itemStyle : { color : colorMap [ 'LA Bachelor' ] } ,
label : { position : 'left' , show : true , fontSize : 12 , color : '#333' }
} ,
{
name : 'Master' ,
itemStyle : { color : colorMap [ 'Master' ] } ,
label : { position : 'right' , show : true , fontSize : 12 , color : '#333' }
} ,
{
name : 'LA Master' ,
itemStyle : { color : colorMap [ 'LA Master' ] } ,
label : { position : 'right' , show : true , fontSize : 12 , color : '#333' }
} ,
{
name : 'kein Master' ,
itemStyle : { color : colorMap [ 'kein Master' ] } ,
label : { position : 'right' , show : true , fontSize : 12 , color : '#333' }
}
] ,
links : links ,
data : dataNodeArray ,
links : linkArray ,
label : {
show : true ,
fontSize : 12 ,
color : '#333'
fontFamily : valueLabelFontFamily ,
color : valueLabelFontColor ,
fontSize : "1em" ,
position : position _valueLabel ,
distance : 1
} ,
lineStyle : {
width : 2 ,
color : 'source'
color : "gradient"
// , opacity: 0.9,
// curveness: 0.3
}
} ,
left : marginLeft ,
top : marginTop ,
right : marginRight ,
bottom : marginBottom ,
width : "100%"
}
} ;
var option2 = {
dataset : {
source : myDataset
} ,
title : {
text : titleText ,
subtext : subtitleText ,
left : getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "title.left" ) , //"center",
top : getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "title.top" ) , //"top",
textStyle : {
fontSize : 20
} ,
subtextStyle : {
fontSize : 15
}
} ,
series : [
{
type : 'pie' , // sunburst
/ * e n c o d e : {
value : measure1Fldname ,
name : dimension1Fldname
} , * /
tooltip : {
trigger : "item"
} ,
stillShowZeroSum : false ,
return option ;
label : {
show : ( getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "valueLabelShow" ) == "true" ) ? true : false , //true
fontWeight : 'bolder' ,
}
function getDistinctColumnValuesAsSankeyNodes ( myRs , fieldname , nodes , captionEmptyNode )
{
var distinctValues = new Array ( ) ;
formatter : '{c}'
myRs . forEach ( row => {
var newName = row [ fieldname ] . toString ( ) ;
if ( newName == "" )
newName = captionEmptyNode ;
} ,
if ( distinctValues . length == 0 )
{
distinctValues . push ( newName ) ;
}
else if ( distinctValues . length > 0 && ! distinctValues . includes ( newName ) )
{
fontSize : 12 ,
radius : [ getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "innerRadius" ) , getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "outerRadius" ) ] , // ['30%', '80%'], //1. Parameter inside radius, macht ihn zum Donut. 2.Parameter definiert den Anteil des Kreises am ganzen Viewport"
color : myPalette ,
itemStyle : {
borderRadius : ( getChartPropertyValue ( currentChartModel . chartPropertiesUsed , "cornerRadiusRounded" ) == "true" ? [ 20 , 5 , 5 , 10 ] : [ ] ) ,
borderColor : '#fff' ,
borderWidth : 2
}
}
]
} ;
distinctValues . push ( newName ) ;
return option ;
}
}
// ✅ Funktion: Erzeugt einen Farbverlauf von sourceColor zu targetColor
function echartsSankeyCreateGradientColor ( sourceColor , targetColor ) {
return {
type : 'linear' ,
x : 0 , y : 0 , x2 : 1 , y2 : 0 ,
colorStops : [
{ offset : 0 , color : sourceColor } ,
{ offset : 1 , color : targetColor }
]
} ;
} ) ;
distinctValues . forEach ( row => {
var nameObj = { name : row } ;
nodes . push ( nameObj ) ;
} ) ;
return nodes ;
}