docs: function parseDSV2ChartData(code, delimiter) {
    // trim all heading/trailing blank lines
    code = trimKeepingTabs(code);
  
    csv.COLUMN_SEPARATOR = delimiter || detectDelimiter(code);
    let dsv = csv.parse(code);
  
    // trim all values in 2D array
    dsv = dsv.map(arr => arr.map(val => val.trim()));
  
    // test a first row for legends. ['anything', '1', '2', '3'] === false, ['anything', 't1', '2', 't3'] === true
    const hasLegends = dsv[0].filter((v, i) => i > 0).reduce((hasNaN, item) => hasNaN || !isNumeric(item), false);
    const legends = hasLegends ? dsv.shift() : [];
  
    // test a first column for categories
    const hasCategories = dsv.slice(1).reduce((hasNaN, row) => hasNaN || !isNumeric(row[0]), false);
    const categories = hasCategories ? dsv.map(arr => arr.shift()) : [];
    if (hasCategories) {
      legends.shift();
    }
  
    // transpose dsv, parse number
    // [['1','2','3']    [[1,4,7]
    //  ['4','5','6'] =>  [2,5,8]
    //  ['7','8','9']]    [3,6,9]]
    dsv = dsv[0].map((t, i) => dsv.map(x => parseFloat(x[i])));
  
    // make series
    const series = dsv.map((data, i) => hasLegends ? {
      name: legends[i],
      data
    } : {
      data
    });
  
    return {
      categories,
      series
    };
  }