Converter: #TSV String to #SVG File

Converter: #TSV String to #SVG File

translate コンバータ: #TSV 文字列 to #SVG ファイル

Converts a numeric TSV string into a stacked bar chart SVG file. The first TSV column is used as the aggregation unit, such as “YYYYMM”, and the first row as data series, such as “Sales Representative” or “Client”.

Auto Step icon
Configs for this Auto Step
StrConfA
A: Set TSV String *#{EL}
StrConfB1
B1: Set Pixel Widths (Unit Bar Total) as CSV (default:60,360,60)#{EL}
StrConfB2
B2: Set Series Color in CSV (default: “#e66,#39f,#f90,#a3c,…”)#{EL}
StrConfB3
B3: Set Max Scale Value (default: 100)#{EL}
StrConfB4
B4: Set Scale Labels in CSV (eg. “0,50,100%”)#{EL}
StrConfB5
B5: Set Value Prefix/Suffix in CSV (eg. “$,”, “,usd”)#{EL}
BoolConfB6
B6: Legend (Color Marker + Series Name), Hide or Show
BoolConfB7
B7: Scale Labels, Hide or Show
SelectConfC
C: Select FILE DATA to store Generated SVG (append) *
StrConfB8
B8: Set SVG File Name (default: chart.svg)#{EL}
SelectConfD1
D1: Select NUMERIC for Number of TSV Lines (update)
SelectConfD2
D2: Select NUMERIC for TSV Rows (ignore blanks) (update)
Script (click to open)
// Script Example of Business Process Automation
// for 'engine type: 3' ("GraalJS standard mode")
main();
function main(){ 
//// == Config Retrieving / 工程コンフィグの参照 ==
const strTsv        = configs.get       ( "StrConfA" );     // REQUIRED
  const arr2dTsv    = parseAsRectangular( strTsv );
  if( arr2dTsv.length === 0 ){
    throw new Error( "\n AutomatedTask ConfigError: Config {A: TSV} is empty \n" );
  }
  if( arr2dTsv.length === 1 ){
    throw new Error( "\n AutomatedTask ConfigError: Config {A: TSV} requires at least 2 lines (header and data) \n" );
  }
const strWidths     = configs.get       ( "StrConfB1" ) || "60,360,60"; // default
const arrWidths     = strWidths.split(",");
const numWidthUnit  = parseInt (arrWidths[0], 10) || 60;
const numWidthBar   = parseInt (arrWidths[1], 10) || 360;
const numWidthTotal = parseInt (arrWidths[2], 10) || 60;
const strColors     = configs.get       ( "StrConfB2" ) || "#e66,#39f,#f90,#a3c,#6a5,#678,#cc3,#999";     // default
const arrColors     = strColors.split(",");
const strMaxScale   = configs.get       ( "StrConfB3" );
const numMaxScale   = strMaxScale !== "" ? parseFloat(strMaxScale) : 100; // default 100
const strLabels     = configs.get       ( "StrConfB4" ) || "0,50,100%";
const arrLabels     = strLabels.split(",");
const strPrefSuf    = configs.get("StrConfB5") || ","; // デフォルトはカンマのみ
const arrPrefSuf    = strPrefSuf.split(",");
const strPrefix     = arrPrefSuf[0] || ""; // カンマの左側
const strSuffix     = arrPrefSuf.length > 1 ? arrPrefSuf[1] : ""; // カンマの右側
const isShowLegend  = configs.getObject ( "BoolConfB6" );
const isShowLabels  = configs.getObject ( "BoolConfB7" );
const strFileName   = configs.get       ( "StrConfB8" ) || "chart.svg";
const filesPocketC  = configs.getObject ( "SelectConfC" ); // REQUIRED
const numPocketD1   = configs.getObject ( "SelectConfD1" );
const numPocketD2   = configs.getObject ( "SelectConfD2" );
//// == Calculating / 演算 (SVG文字列の生成) ==
const marginL = 15; // 左側の余白
const marginR = 15; // 右側の余白
const svgTotalWidth = marginL + numWidthUnit + numWidthBar + numWidthTotal + marginR;
let svgY = 10; // Y座標の初期オフセット
let svgContent = `<style>text { font-family: sans-serif; font-size: 12px; fill: #333; }</style>\n`;
// XMLエスケープ用関数
function escapeXml(unsafe) {
    return unsafe.replace(/[<>&'"]/g, function (c) {
        switch (c) {
            case '<': return '&lt;';
            case '>': return '&gt;';
            case '&': return '&amp;';
            case '\'': return '&apos;';
            case '"': return '&quot;';
        }
    });
}
// --- 1. 凡例の描画 ---
if (isShowLegend) {
  let lx = marginL;
  for (let i = 1; i < arr2dTsv[0].length; i++) {
    const color = arrColors[(i - 1) % arrColors.length];
    const seriesName = escapeXml(arr2dTsv[0][i]);
    // 簡易的な文字幅推測(長すぎたら改行)
    const estimateW = seriesName.length * 12 + 25; 
    if (lx + estimateW > svgTotalWidth - marginR) {
      lx = marginL;
      svgY += 20;
    }
    svgContent += `<rect x="${lx}" y="${svgY}" width="10" height="10" fill="${color}" rx="2"/>\n`;
    svgContent += `<text x="${lx + 16}" y="${svgY + 9}">${seriesName}</text>\n`;
    lx += estimateW + 10;
  }
  svgY += 25; // 下部マージン
}
// --- 2. 目盛りの描画 ---
if (isShowLabels) {
  const stepW = numWidthBar / (arrLabels.length - 1 || 1);
  for (let i = 0; i < arrLabels.length; i++) {
    const xPos = marginL + numWidthUnit + (stepW * i);
    let anchor = i === 0 ? "start" : i === arrLabels.length - 1 ? "end" : "middle";
    svgContent += `<text x="${xPos}" y="${svgY + 10}" fill="#777" text-anchor="${anchor}">${escapeXml(arrLabels[i])}</text>\n`;
  }
  svgY += 20;
}
// --- 3. 棒グラフの描画 ---
const rowHeight = 18; // 1行の高さ (MdChartの間隔に寄せるため短縮)
for (let i = 1; i < arr2dTsv.length; i++) {
  // ユニット名(左側)
  svgContent += `<text x="${marginL}" y="${svgY + 13}">${escapeXml(arr2dTsv[i][0])}</text>\n`;
  // グラフ背景(グレー) ※MdChartに合わせて高さを10pxに変更
  svgContent += `<rect x="${marginL + numWidthUnit}" y="${svgY + 4}" width="${numWidthBar}" height="10" fill="#eee" rx="2"/>\n`;
  let currentX = marginL + numWidthUnit;
  let totalValue = 0;
  // 系列ごとの積み上げ描画
  for (let j = 1; j < arr2dTsv[i].length; j++) {
    const cellValue = parseFloat(arr2dTsv[i][j].replace(/[^0-9.-]/g, "")) || 0;
    totalValue += cellValue;
    const barW = Math.max(0, (cellValue / numMaxScale) * numWidthBar);
    
    if (barW > 0) {
      const color = arrColors[(j - 1) % arrColors.length];
      svgContent += `<rect x="${currentX}" y="${svgY + 4}" width="${barW}" height="10" fill="${color}"/>\n`;
      currentX += barW;
    }
  }
  // 合計値(右側)
  const totalStr = `${strPrefix}${totalValue.toLocaleString()}${strSuffix}`;
  svgContent += `<text x="${marginL + numWidthUnit + numWidthBar + 10}" y="${svgY + 13}" text-anchor="start">${escapeXml(totalStr)}</text>\n`;
  svgY += rowHeight; // 次の行へY座標を移動
}
// --- 4. 全体をSVGタグでラップ ---
const finalSvgHeight = svgY + 10;
let strSvg = `<?xml version="1.0" encoding="UTF-8"?>\n`;
strSvg += `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svgTotalWidth} ${finalSvgHeight}" width="${svgTotalWidth}" height="${finalSvgHeight}">\n`;
strSvg += `<rect width="100%" height="100%" fill="#ffffff"/>\n`; // 背景の透過防止用白塗り
strSvg += svgContent;
strSvg += `</svg>`;
//// == Data Updating / ワークフローデータへの代入 ==
// SVGファイルの保存 (C)
if (filesPocketC !== null) { 
  let filesSvg = engine.findData(filesPocketC);
  if (filesSvg === null) {
    filesSvg = new java.util.ArrayList();
  }
  
  // 指定されたファイル名、なければ "chart.svg"
  let finalFileName = strFileName;
  if (!finalFileName.toLowerCase().endsWith(".svg")) {
      finalFileName += ".svg";
  }
  filesSvg.add(
    new com.questetra.bpms.core.event.scripttask.NewQfile(
      finalFileName,
      "image/svg+xml; charset=UTF-8",
      strSvg
    )
  );
  engine.setData(filesPocketC, filesSvg);
}
// オリジナルのTSV文字列の行数 (D1)
if ( numPocketD1 !== null) { 
  const totalLines = strTsv.split("\n").length;
  engine.setData( numPocketD1, new java.math.BigDecimal(totalLines) );
}
// 空行を除いたTSV行数 (D2)
if ( numPocketD2 !== null) { 
  engine.setData( numPocketD2, new java.math.BigDecimal(arr2dTsv.length) );
}
} //////// END "main()" /////////////////////////////////////////////////////////////////
/**
 * Parses TSV string as a two-dimensional rectangular data matrix and creates a 2D array.
 * @param {string} strTsv - The input TSV string
 * @returns {Array<Array<string>>} Rectangular 2D array
 */
function parseAsRectangular( strTsv ){
  const arrTsv = strTsv.split("\n");
  let numMinWidth   = Infinity;
  let numMaxWidth   = 0;
  let numBlanklines = 0;
  
  const parsedRows = [];
  for( let i = 0; i < arrTsv.length; i++ ){
    const line = arrTsv[i];
    if( line === "" ){
      numBlanklines++;
      continue;
    }
    const arrCells = line.split("\t");
    const len = arrCells.length;
    if( len < numMinWidth ){ numMinWidth = len; }
    if( len > numMaxWidth ){ numMaxWidth = len; }
    parsedRows.push( arrCells );
  }
  
  if( numMinWidth === Infinity ){
    numMinWidth = 0;
  }
  engine.log( " AutomatedTask TsvDataCheck:" + 
              " MinWidth:" + numMinWidth +
              " MaxWidth:" + numMaxWidth +
              " Lines:" + arrTsv.length +
              " (BlankLines:" + numBlanklines + ")" );
  for( let i = 0; i < parsedRows.length; i++ ){
    const row = parsedRows[i];
    while( row.length < numMaxWidth ){
      row.push( "" );
    }
  }
  return parsedRows;
}
/*
### NOTES-en
- A bar chart (SVG file) is automatically generated each time a Case reaches this Automated Step.
    - From the second row of the input TSV string onward, all fields are parsed as "numeric strings", except for the first column (Column A).
        - Only the first column (Column A) is considered as the aggregation unit name, such as "YYYY-MM" or "Quarter".
        - If characters other than a "period (.)", a "minus sign (-)", and "0-9" exist, the "stripped string" is parsed.
        - Even if commas exist, they are considered thousands separators, and the "stripped string" is parsed.
        - The "stripped string" is parsed using `parseFloat()`.
        - If parsing fails, the value is evaluated as `0`.
    - The generated "SVG file" will be added to the specified File-type data item.
        - For an N-row, 2-column TSV string, a simple bar chart is displayed (e.g., "Monthly Sales").
        - For an N-row, M-column TSV string, a stacked bar chart with (M-1) "series" is displayed.
        - The width of each series is calculated and drawn based on the "Max Scale Value (B3)".
- The input TSV is parsed as the "simplest tab-separated string".
- The number of lines in the input TSV string can also be saved as Case Data.
### NOTES-ja
- このアドオン自動工程にケース(案件)が到達する度に、自動的に棒グラフ(SVGファイル)が生成されます。
    - 入力TSV文字列の2行目以降は、全フィールドが「数値文字列」として解析されます。※1列目(A列)を除く
        - 1列目(A列)だけは、「年月」や「四半期」などの集計単位名とみなされます。
        - 「ピリオド(.)」と「マイナス記号」と「0~9」以外の文字が存在する場合、「除去された文字列」が解析されます。
        - カンマが存在する場合も、(桁区切り文字とみなされ)、「除去された文字列」が解析されます。
        - 「除去された文字列」が `parseFloat()` によって解析されます。
        - 解析に失敗した場合 `0` とみなされます。
    - 生成された "SVGファイル" は、指定されたファイル型データ項目に追加保存されます。
        - N行2列TSV文字列の場合、シンプル棒グラフが表示されます。 ※「月ごとの売上」など
        - N行M列TSV文字列の場合、"系列" が (M-1) 個の積み上げ棒グラフが表示されます。
        - 各系列の横幅は「グラフ目盛りの最大値(B3)」を基準に算出・描画されます。
- 入力TSVは「もっともシンプルなタブ区切り文字列」としてパースされます。
- 入力TSV文字列の行数もケースデータとして格納可能です。
#### TSV example
YYYY-MM	Tanaka	Sato	Suzuki	Yamada
2026-01	610,000	520,000	380,000	450,000
2026-02	580,000	490,000	410,000	480,000
2026-03	700,000	600,000	450,000	550,000
2026-04	550,000	480,000	390,000	420,000
2026-05	590,000	510,000	420,000	460,000
2026-06	630,000	550,000	460,000	510,000
2026-07	680,000	620,000	250,000	280,000
2026-08	570,000	530,000	230,000	290,000
2026-09	650,000	310,000	220,000	250,000
2026-10	560,000	290,000	240,000	270,000
2026-11	600,000	300,000	250,000	260,000
2026-12	800,000	720,000	600,000	680,000
### APPENDIX-en
- Blank lines within the input TSV string are ignored.
- Please set series colors corresponding to the number of series in the TSV (TSV column count - 1).
    - (It is fine to set more colors than required)
    - Example of high-contrast series colors: "#e66,#39f,#f90,#a3c,#6a5,#678,#cc3,#999".
    - Example of warm/cool color gradation: "#c42,#e84,#fb6,#cc3,#6ad,#9cf,#cde,#eef".
- Special characters like `<`, `>`, `&` in the TSV string will be automatically XML-escaped to prevent SVG file corruption.
- You can specify a prefix and suffix for the total value.
    - To add only a prefix: Set `$,` → `$1,200,000`
    - To add only a suffix: Set `, items` → `150 items`
    - To add both: Set `Approx. , hours` → `Approx. 40 hours`
    - To include a space: Set `$ ,` → `$ 1,200`
### APPENDIX-ja
- 入力TSV文字列内の空行は無視されます。
- 系列の色は、TSV内の系列の数(TSV列数 - 1)だけセットしてください。(多く設定する分には構いません)
    - 系列のメリハリを効かせる例: "#e66,#39f,#f90,#a3c,#6a5,#678,#cc3,#999"
    - 寒色暖色でグラデーション例: "#c42,#e84,#fb6,#cc3,#6ad,#9cf,#cde,#eef"
- TSV内の `<` や `&` といった特殊記号は、SVGファイルの破損を防ぐために自動的にXMLエスケープされます。
- 合計値には接頭辞・接尾辞を指定できます。
    - 前だけにつけたい場合: `¥,` と設定 → `¥1,200,000`
    - 後ろだけにつけたい場合: `,件` と設定 → `150件`
    - 前後両方につけたい場合: `約,時間` と設定 → `約40時間`
    - 間にスペースを入れたい場合: `$ ,` と設定 → `$ 1,200`
*/

Download

warning Freely modifiable JavaScript (ECMAScript) code. No warranty of any kind.
(Installing Addon Auto-Steps are available only on the Professional edition.)

Notes

  • A bar chart (SVG file) is automatically generated each time a Case reaches this Automated Step.
    • From the second row of the input TSV string onward, all fields are parsed as “numeric strings”, except for the first column (Column A).
      • Only the first column (Column A) is considered as the aggregation unit name, such as “YYYY-MM” or “Quarter”.
      • If characters other than a “period (.)”, a “minus sign (-)”, and “0-9” exist, the “stripped string” is parsed.
      • Even if commas exist, they are considered thousands separators, and the “stripped string” is parsed.
      • The “stripped string” is parsed using parseFloat().
      • If parsing fails, the value is evaluated as 0.
    • The generated “SVG file” will be added to the specified File-type data item.
      • For an N-row, 2-column TSV string, a simple bar chart is displayed (e.g., “Monthly Sales”).
      • For an N-row, M-column TSV string, a stacked bar chart with (M-1) “series” is displayed.
      • The width of each series is calculated and drawn based on the “Max Scale Value (B3)”.
  • The input TSV is parsed as the “simplest tab-separated string”.
  • The number of lines in the input TSV string can also be saved as Case Data.

Capture

Appendix

  • Blank lines within the input TSV string are ignored.
  • Please set series colors corresponding to the number of series in the TSV (TSV column count – 1).
    • (It is fine to set more colors than required)
    • Example of high-contrast series colors: “#e66,#39f,#f90,#a3c,#6a5,#678,#cc3,#999”.
    • Example of warm/cool color gradation: “#c42,#e84,#fb6,#cc3,#6ad,#9cf,#cde,#eef”.
  • Special characters like <, >, & in the TSV string will be automatically XML-escaped to prevent SVG file corruption.
  • You can specify a prefix and suffix for the total value.
    • To add only a prefix: Set $,$1,200,000
    • To add only a suffix: Set , items150 items
    • To add both: Set Approx. , hoursApprox. 40 hours
    • To include a space: Set $ ,$ 1,200

See Also

Discover more from Questetra Support

Subscribe now to keep reading and get access to the full archive.

Continue reading