Browse Source

Sankey chart mit Echarts #17

master
Daniel Quathamer 3 days ago
parent
commit
5388a890af
  1. 3
      src-modules/module/viz/conf/viz.xml
  2. 53
      src-modules/module/viz/schluesseltabellen/viz_properties_echarts_fuellen.sql
  3. 13
      src-modules/module/viz/schluesseltabellen/viz_properties_echarts_sankey_fuellen.sql
  4. 3
      src-modules/module/viz/upgrade/viz_upgrade_man.sql
  5. 271
      superx/xml/js/viz/viz_viewer.js

3
src-modules/module/viz/conf/viz.xml

@ -1181,6 +1181,8 @@ detail_chart_uniquename varchar(255) @@ -1181,6 +1181,8 @@ detail_chart_uniquename varchar(255)
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_line_bar_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_pie_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_sankey_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_d3js_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_d3js_pie_fuellen.sql" database=""/>
@ -1373,6 +1375,7 @@ detail_chart_uniquename varchar(255) @@ -1373,6 +1375,7 @@ detail_chart_uniquename varchar(255)
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_line_bar_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_pie_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_echarts_sankey_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_d3js_fuellen.sql" database=""/>
<nativeaction sql="" scriptfile="$VIZ_PFAD/schluesseltabellen/viz_properties_d3js_pie_fuellen.sql" database=""/>

53
src-modules/module/viz/schluesseltabellen/viz_properties_echarts_fuellen.sql

@ -758,7 +758,7 @@ @@ -758,7 +758,7 @@
"parent_property_uniquename":"valueLabelNode",
"variableName":"distance_y"
},
{ "caption":"Ausrichtung Wertelabel (vertikal)",
{ "caption":"Ausrichtung Wertelabel",
"prop_uniquename":"position_valueLabel",
"explanation":"",
"prop_default":"inside",
@ -809,6 +809,57 @@ @@ -809,6 +809,57 @@
"parent_property_uniquename":"valueLabelNode",
"variableName":"offset_y_valueLabel"
},
{ "caption":"Wertelabel Schriftart",
"prop_uniquename":"valueLabelFontFamily",
"explanation":"Wertelabel Schriftart",
"prop_default":"sans-serif",
"prop_unit":"",
"is_generic":"1",
"static_values":"",
"is_mandatory":"0",
"input_type_uniquename":"TEXT",
"property_group_uniquename":"",
"sortnr":"460",
"range_from":"",
"range_to":"",
"prop_value_type":"STRING",
"parent_property_uniquename":"valueLabelNode",
"variableName":"valueLabelFontFamily"
},
{ "caption":"Wertelabel Schriftgröße",
"prop_uniquename":"valueLabelFontSize",
"explanation":"Wertelabel Schriftgröße",
"prop_default":"1em",
"prop_unit":"",
"is_generic":"1",
"static_values":"",
"is_mandatory":"0",
"input_type_uniquename":"TEXT",
"property_group_uniquename":"",
"sortnr":"460",
"range_from":"",
"range_to":"",
"prop_value_type":"STRING",
"parent_property_uniquename":"valueLabelNode",
"variableName":"valueLabelFontSize"
},
{ "caption":"Wertelabel Schriftfarbe",
"prop_uniquename":"valueLabelFontColor",
"explanation":"Wertelabel Schriftfarbe",
"prop_default":"1em",
"prop_unit":"",
"is_generic":"1",
"static_values":"",
"is_mandatory":"0",
"input_type_uniquename":"COLOR",
"property_group_uniquename":"",
"sortnr":"480",
"range_from":"",
"range_to":"",
"prop_value_type":"STRING",
"parent_property_uniquename":"valueLabelNode",
"variableName":"valueLabelFontColor"
},
{ "caption":"Diagrammelement",
"prop_uniquename":"seriesNode",
"explanation":"Layout der Diagrammelemente",

13
src-modules/module/viz/schluesseltabellen/viz_properties_echarts_sankey_fuellen.sql

@ -23,7 +23,18 @@ @@ -23,7 +23,18 @@
"is_mandatory":"1"},
{"viz_property_uniquename":"measure1",
"is_mandatory":"1"},
{"viz_property_uniquename":"nodeGap"}
{"viz_property_uniquename":"null_value_mask",
"is_mandatory":"0"},
{"viz_property_uniquename":"margin.left"},
{"viz_property_uniquename":"margin.top"},
{"viz_property_uniquename":"margin.right"},
{"viz_property_uniquename":"margin.bottom"},
{"viz_property_uniquename":"valueLabelShow"},
{"viz_property_uniquename":"valueLabelFontFamily"},
{"viz_property_uniquename":"valueLabelFontColor"},
{"viz_property_uniquename":"valueLabelFontSize"},
{"viz_property_uniquename":"position_valueLabel"}
]
/>

3
src-modules/module/viz/upgrade/viz_upgrade_man.sql

@ -59,3 +59,6 @@ where T.uniquename in ('echarts_bar_x_series','echarts_bar_y_series','echarts_li @@ -59,3 +59,6 @@ where T.uniquename in ('echarts_bar_x_series','echarts_bar_y_series','echarts_li
delete from viz_type
where uniquename in ('echarts_bar_x_series','echarts_bar_y_series','echarts_line_x_series','echarts_line_y_series');
--Sankey nicht mehr über d3js, sondern Echarts:
delete from viz_chart where is_custom=0
and uniquename='ERFOLG_BA_MA_SANKEY';

271
superx/xml/js/viz/viz_viewer.js

@ -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";
/*const sourceData = [
{"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;
var nodeName = params.name;
return nodeName + ': ' + params.value.toLocaleString();
// 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;
else
{ //link:
return params.data.source + ' --> ' + params.data.target + ': ' + params.data.value.toLocaleString();
}
}
},
series: {
name: 'Bachelor-Master Übergänge',
name: titleText,
type: 'sankey',
layout: 'none',
nodeGap: 25, // ← Erhöhter vertikaler Abstand
nodeWidth: 60, // ← Breite der Knoten
emphasis: {
focus: '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' }
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()
}
},
{
name: 'LA Master',
itemStyle: { color: colorMap['LA Master'] },
label: { position: 'right', show: true, fontSize: 12, color: '#333' }
emphasis: {
disabled:true
//focus: 'self' //'adjacency'
},
{
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
}
}
};
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
}
},
left:marginLeft,
top:marginTop,
right:marginRight,
bottom:marginBottom,
width:"100%"
series: [
{
type: 'pie',// sunburst
/*encode: {
value: measure1Fldname,
name: dimension1Fldname
},*/
tooltip: {
trigger: "item"
},
stillShowZeroSum: false,
}
};
label: {
show: (getChartPropertyValue(currentChartModel.chartPropertiesUsed,"valueLabelShow")=="true")?true:false, //true
fontWeight:'bolder',
return option;
formatter: '{c}'
}
function getDistinctColumnValuesAsSankeyNodes(myRs,fieldname,nodes,captionEmptyNode)
{
var distinctValues=new Array();
} ,
myRs.forEach(row => {
var newName = row[fieldname].toString();
if(newName=="")
newName=captionEmptyNode;
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
if( distinctValues.length==0)
{
distinctValues.push(newName);
}
else if( distinctValues.length>0 && !distinctValues.includes(newName))
{
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;
}

Loading…
Cancel
Save