gen-charts.ts 99 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042
  1. /**
  2. * PptxGenJS: Chart Generation
  3. */
  4. import {
  5. AXIS_ID_CATEGORY_PRIMARY,
  6. AXIS_ID_CATEGORY_SECONDARY,
  7. AXIS_ID_SERIES_PRIMARY,
  8. AXIS_ID_VALUE_PRIMARY,
  9. AXIS_ID_VALUE_SECONDARY,
  10. BARCHART_COLORS,
  11. CHART_NAME,
  12. CHART_TYPE,
  13. DEF_CHART_GRIDLINE,
  14. DEF_FONT_COLOR,
  15. DEF_FONT_SIZE,
  16. DEF_FONT_TITLE_SIZE,
  17. DEF_SHAPE_SHADOW,
  18. LETTERS,
  19. ONEPT,
  20. } from './core-enums'
  21. import { IChartOptsLib, ISlideRelChart, ShadowProps, IChartPropsTitle, OptsChartGridLine, IOptsChartData, ChartLineCap } from './core-interfaces'
  22. import { createColorElement, genXmlColorSelection, convertRotationDegrees, encodeXmlEntities, getUuid, valToPts } from './gen-utils'
  23. import JSZip from 'jszip'
  24. /**
  25. * Based on passed data, creates Excel Worksheet that is used as a data source for a chart.
  26. * @param {ISlideRelChart} chartObject - chart object
  27. * @param {JSZip} zip - file that the resulting XLSX should be added to
  28. * @return {Promise} promise of generating the XLSX file
  29. */
  30. export async function createExcelWorksheet (chartObject: ISlideRelChart, zip: JSZip): Promise<string> {
  31. const data = chartObject.data
  32. return await new Promise((resolve, reject) => {
  33. const zipExcel = new JSZip()
  34. const intBubbleCols = (data.length - 1) * 2 + 1 // 1 for "X-Values", then 2 for every Y-Axis
  35. const IS_MULTI_CAT_AXES = data[0]?.labels?.length > 1
  36. // A: Add folders
  37. zipExcel.folder('_rels')
  38. zipExcel.folder('docProps')
  39. zipExcel.folder('xl/_rels')
  40. zipExcel.folder('xl/tables')
  41. zipExcel.folder('xl/theme')
  42. zipExcel.folder('xl/worksheets')
  43. zipExcel.folder('xl/worksheets/_rels')
  44. // B: Add core contents
  45. {
  46. zipExcel.file(
  47. '[Content_Types].xml',
  48. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
  49. ' <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
  50. ' <Default Extension="xml" ContentType="application/xml"/>' +
  51. ' <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
  52. ' <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
  53. ' <Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>' +
  54. ' <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>' +
  55. ' <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>' +
  56. ' <Override PartName="/xl/tables/table1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"/>' +
  57. ' <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' +
  58. ' <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' +
  59. '</Types>\n'
  60. )
  61. zipExcel.file(
  62. '_rels/.rels',
  63. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
  64. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>' +
  65. '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>' +
  66. '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
  67. '</Relationships>\n'
  68. )
  69. zipExcel.file(
  70. 'docProps/app.xml',
  71. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">' +
  72. '<Application>Microsoft Macintosh Excel</Application>' +
  73. '<DocSecurity>0</DocSecurity>' +
  74. '<ScaleCrop>false</ScaleCrop>' +
  75. '<HeadingPairs><vt:vector size="2" baseType="variant"><vt:variant><vt:lpstr>Worksheets</vt:lpstr></vt:variant><vt:variant><vt:i4>1</vt:i4></vt:variant></vt:vector></HeadingPairs>' +
  76. '<TitlesOfParts><vt:vector size="1" baseType="lpstr"><vt:lpstr>Sheet1</vt:lpstr></vt:vector></TitlesOfParts>' +
  77. '<Company></Company><LinksUpToDate>false</LinksUpToDate><SharedDoc>false</SharedDoc><HyperlinksChanged>false</HyperlinksChanged><AppVersion>16.0300</AppVersion>' +
  78. '</Properties>\n'
  79. )
  80. zipExcel.file(
  81. 'docProps/core.xml',
  82. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
  83. '<dc:creator>PptxGenJS</dc:creator>' +
  84. '<cp:lastModifiedBy>PptxGenJS</cp:lastModifiedBy>' +
  85. '<dcterms:created xsi:type="dcterms:W3CDTF">' +
  86. new Date().toISOString() +
  87. '</dcterms:created>' +
  88. '<dcterms:modified xsi:type="dcterms:W3CDTF">' +
  89. new Date().toISOString() +
  90. '</dcterms:modified>' +
  91. '</cp:coreProperties>'
  92. )
  93. zipExcel.file(
  94. 'xl/_rels/workbook.xml.rels',
  95. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
  96. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
  97. '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' +
  98. '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>' +
  99. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' +
  100. '<Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>' +
  101. '</Relationships>'
  102. )
  103. zipExcel.file(
  104. 'xl/styles.xml',
  105. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><numFmts count="1"><numFmt numFmtId="0" formatCode="General"/></numFmts><fonts count="4"><font><sz val="9"/><color indexed="8"/><name val="Geneva"/></font><font><sz val="9"/><color indexed="8"/><name val="Geneva"/></font><font><sz val="10"/><color indexed="8"/><name val="Geneva"/></font><font><sz val="18"/><color indexed="8"/>' +
  106. '<name val="Arial"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><dxfs count="0"/><tableStyles count="0"/><colors><indexedColors><rgbColor rgb="ff000000"/><rgbColor rgb="ffffffff"/><rgbColor rgb="ffff0000"/><rgbColor rgb="ff00ff00"/><rgbColor rgb="ff0000ff"/>' +
  107. '<rgbColor rgb="ffffff00"/><rgbColor rgb="ffff00ff"/><rgbColor rgb="ff00ffff"/><rgbColor rgb="ff000000"/><rgbColor rgb="ffffffff"/><rgbColor rgb="ff878787"/><rgbColor rgb="fff9f9f9"/></indexedColors></colors></styleSheet>\n'
  108. )
  109. zipExcel.file(
  110. 'xl/theme/theme1.xml',
  111. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="44546A"/></a:dk2><a:lt2><a:srgbClr val="E7E6E6"/></a:lt2><a:accent1><a:srgbClr val="4472C4"/></a:accent1><a:accent2><a:srgbClr val="ED7D31"/></a:accent2><a:accent3><a:srgbClr val="A5A5A5"/></a:accent3><a:accent4><a:srgbClr val="FFC000"/></a:accent4><a:accent5><a:srgbClr val="5B9BD5"/></a:accent5><a:accent6><a:srgbClr val="70AD47"/></a:accent6><a:hlink><a:srgbClr val="0563C1"/></a:hlink><a:folHlink><a:srgbClr val="954F72"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Calibri Light" panose="020F0302020204030204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="Yu Gothic Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="DengXian Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:majorFont><a:minorFont><a:latin typeface="Calibri" panose="020F0502020204030204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="Yu Gothic"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="DengXian"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{62F939B6-93AF-4DB8-9C6B-D6C7DFDC589F}" vid="{4A3C46E8-61CC-4603-A589-7422A47A8E4A}"/></a:ext></a:extLst></a:theme>'
  112. )
  113. zipExcel.file(
  114. 'xl/workbook.xml',
  115. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
  116. '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main">' +
  117. '<fileVersion appName="xl" lastEdited="7" lowestEdited="6" rupBuild="10507"/>' +
  118. '<workbookPr/>' +
  119. '<bookViews><workbookView xWindow="0" yWindow="500" windowWidth="20960" windowHeight="15960"/></bookViews>' +
  120. '<sheets><sheet name="Sheet1" sheetId="1" r:id="rId1"/></sheets>' +
  121. '<calcPr calcId="0" concurrentCalc="0"/>' +
  122. '</workbook>\n'
  123. )
  124. zipExcel.file(
  125. 'xl/worksheets/_rels/sheet1.xml.rels',
  126. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
  127. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
  128. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" Target="../tables/table1.xml"/>' +
  129. '</Relationships>\n'
  130. )
  131. }
  132. // sharedStrings.xml
  133. {
  134. // A: Start XML
  135. let strSharedStrings = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
  136. if (chartObject.opts._type === CHART_TYPE.BUBBLE || chartObject.opts._type === CHART_TYPE.BUBBLE3D) {
  137. strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${intBubbleCols}" uniqueCount="${intBubbleCols}">`
  138. } else if (chartObject.opts._type === CHART_TYPE.SCATTER) {
  139. strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${data.length}" uniqueCount="${data.length}">`
  140. } else if (IS_MULTI_CAT_AXES) {
  141. let totCount = data.length
  142. data[0].labels.forEach(arrLabel => (totCount += arrLabel.filter(label => label && label !== '').length))
  143. strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${totCount}" uniqueCount="${totCount}">`
  144. strSharedStrings += '<si><t/></si>'
  145. } else {
  146. // series names + all labels of one series + number of label groups (data.labels.length) of one series (i.e. how many times the blank string is used)
  147. const totCount = data.length + data[0].labels.length * data[0].labels[0].length + data[0].labels.length
  148. // series names + labels of one series + blank string (same for all label groups)
  149. const unqCount = data.length + data[0].labels.length * data[0].labels[0].length + 1
  150. // start `sst`
  151. strSharedStrings += `<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${totCount}" uniqueCount="${unqCount}">`
  152. // B: Add 'blank' for A1, B1, ..., of every label group inside data[n].labels
  153. strSharedStrings += '<si><t xml:space="preserve"></t></si>'
  154. }
  155. // C: Add `name`/Series
  156. if (chartObject.opts._type === CHART_TYPE.BUBBLE || chartObject.opts._type === CHART_TYPE.BUBBLE3D) {
  157. data.forEach((objData, idx) => {
  158. if (idx === 0) strSharedStrings += '<si><t>X-Axis</t></si>'
  159. else {
  160. strSharedStrings += `<si><t>${encodeXmlEntities(objData.name || `Y-Axis${idx}`)}</t></si>`
  161. strSharedStrings += `<si><t>${encodeXmlEntities(`Size${idx}`)}</t></si>`
  162. }
  163. })
  164. } else {
  165. data.forEach(objData => {
  166. strSharedStrings += `<si><t>${encodeXmlEntities((objData.name || ' ').replace('X-Axis', 'X-Values'))}</t></si>`
  167. })
  168. }
  169. // D: Add `labels`/Categories
  170. if (chartObject.opts._type !== CHART_TYPE.BUBBLE && chartObject.opts._type !== CHART_TYPE.BUBBLE3D && chartObject.opts._type !== CHART_TYPE.SCATTER) {
  171. // Use forEach backwards & check for '' to support multi-cat axes
  172. data[0].labels
  173. .slice()
  174. .reverse()
  175. .forEach(labelsGroup => {
  176. labelsGroup
  177. .filter(label => label && label !== '')
  178. .forEach(label => {
  179. strSharedStrings += `<si><t>${encodeXmlEntities(label)}</t></si>`
  180. })
  181. })
  182. }
  183. // DONE:
  184. strSharedStrings += '</sst>\n'
  185. zipExcel.file('xl/sharedStrings.xml', strSharedStrings)
  186. }
  187. // tables/table1.xml
  188. {
  189. let strTableXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
  190. if (chartObject.opts._type === CHART_TYPE.BUBBLE || chartObject.opts._type === CHART_TYPE.BUBBLE3D) {
  191. strTableXml += `<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="Table1" displayName="Table1" ref="A1:${getExcelColName(intBubbleCols)}${intBubbleCols}" totalsRowShown="0">`
  192. strTableXml += `<tableColumns count="${intBubbleCols}">`
  193. let idxColLtr = 1
  194. data.forEach((obj, idx) => {
  195. if (idx === 0) {
  196. strTableXml += `<tableColumn id="${idx + 1}" name="X-Values"/>`
  197. } else {
  198. strTableXml += `<tableColumn id="${idx + idxColLtr}" name="${obj.name}"/>`
  199. idxColLtr++
  200. strTableXml += `<tableColumn id="${idx + idxColLtr}" name="Size${idx}"/>`
  201. }
  202. })
  203. } else if (chartObject.opts._type === CHART_TYPE.SCATTER) {
  204. strTableXml += `<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="Table1" displayName="Table1" ref="A1:${getExcelColName(data.length)}${data[0].values.length + 1}" totalsRowShown="0">`
  205. strTableXml += `<tableColumns count="${data.length}">`
  206. data.forEach((_obj, idx) => {
  207. strTableXml += `<tableColumn id="${idx + 1}" name="${idx === 0 ? 'X-Values' : 'Y-Value '}${idx}"/>`
  208. })
  209. } else {
  210. strTableXml += `<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="Table1" displayName="Table1" ref="A1:${getExcelColName(data.length + data[0].labels.length)}${data[0].labels[0].length + 1}'" totalsRowShown="0">`
  211. strTableXml += `<tableColumns count="${data.length + data[0].labels.length}">`
  212. data[0].labels.forEach((_labelsGroup, idx) => {
  213. strTableXml += `<tableColumn id="${idx + 1}" name="Column${idx + 1}"/>`
  214. })
  215. data.forEach((obj, idx) => {
  216. strTableXml += `<tableColumn id="${idx + data[0].labels.length + 1}" name="${encodeXmlEntities(obj.name)}"/>`
  217. })
  218. }
  219. strTableXml += '</tableColumns>'
  220. strTableXml += '<tableStyleInfo showFirstColumn="0" showLastColumn="0" showRowStripes="1" showColumnStripes="0"/>'
  221. strTableXml += '</table>'
  222. zipExcel.file('xl/tables/table1.xml', strTableXml)
  223. }
  224. // worksheets/sheet1.xml
  225. {
  226. let strSheetXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
  227. strSheetXml +=
  228. '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">'
  229. if (chartObject.opts._type === CHART_TYPE.BUBBLE || chartObject.opts._type === CHART_TYPE.BUBBLE3D) {
  230. strSheetXml += `<dimension ref="A1:${getExcelColName(intBubbleCols)}${data[0].values.length + 1}"/>`
  231. } else if (chartObject.opts._type === CHART_TYPE.SCATTER) {
  232. strSheetXml += `<dimension ref="A1:${getExcelColName(data.length)}${data[0].values.length + 1}"/>`
  233. } else {
  234. strSheetXml += `<dimension ref="A1:${getExcelColName(data.length + 1)}${data[0].values.length + 1}"/>`
  235. }
  236. strSheetXml += '<sheetViews><sheetView tabSelected="1" workbookViewId="0"><selection activeCell="B1" sqref="B1"/></sheetView></sheetViews>'
  237. strSheetXml += '<sheetFormatPr baseColWidth="10" defaultRowHeight="16"/>'
  238. if (chartObject.opts._type === CHART_TYPE.BUBBLE || chartObject.opts._type === CHART_TYPE.BUBBLE3D) {
  239. // UNUSED: strSheetXml += `<cols><col min="1" max="${data.length}" width="11" customWidth="1" /></cols>`
  240. /* EX: INPUT: `data`
  241. [
  242. { name:'X-Axis' , values:[10,11,12,13,14,15,16,17,18,19,20] },
  243. { name:'Y-Axis 1', values:[ 1, 6, 7, 8, 9], sizes:[ 4, 5, 6, 7, 8] },
  244. { name:'Y-Axis 2', values:[33,32,42,53,63], sizes:[11,12,13,14,15] }
  245. ];
  246. */
  247. /* EX: OUTPUT: bubbleChart Worksheet:
  248. -|----A-----|------B-----|------C-----|------D-----|------E-----|
  249. 1| X-Values | Y-Values 1 | Y-Sizes 1 | Y-Values 2 | Y-Sizes 2 |
  250. 2| 11 | 22 | 4 | 33 | 8 |
  251. -|----------|------------|------------|------------|------------|
  252. */
  253. strSheetXml += '<sheetData>'
  254. // A: Create header row first (NOTE: Start at index=1 as headers cols start with 'B')
  255. strSheetXml += `<row r="1" spans="1:${intBubbleCols}">`
  256. strSheetXml += '<c r="A1" t="s"><v>0</v></c>'
  257. for (let idx = 1; idx < intBubbleCols; idx++) {
  258. strSheetXml += `<c r="${getExcelColName(idx + 1)}1" t="s"><v>${idx}</v></c>` // NOTE: add `t="s"` for label cols!
  259. }
  260. strSheetXml += '</row>'
  261. // B: Add row for each X-Axis value (Y-Axis* value is optional)
  262. data[0].values.forEach((val, idx) => {
  263. // Leading col is reserved for the 'X-Axis' value, so hard-code it, then loop over col values
  264. strSheetXml += `<row r="${idx + 2}" spans="1:${intBubbleCols}">`
  265. strSheetXml += `<c r="A${idx + 2}"><v>${val}</v></c>`
  266. // Add Y-Axis 1->N (idy=0 = Xaxis)
  267. let idxColLtr = 2
  268. for (let idy = 1; idy < data.length; idy++) {
  269. // y-value
  270. strSheetXml += `<c r="${getExcelColName(idxColLtr)}${idx + 2}"><v>${data[idy].values[idx] || ''}</v></c>`
  271. idxColLtr++
  272. // y-size
  273. strSheetXml += `<c r="${getExcelColName(idxColLtr)}${idx + 2}"><v>${data[idy].sizes[idx] || ''}</v></c>`
  274. idxColLtr++
  275. }
  276. strSheetXml += '</row>'
  277. })
  278. } else if (chartObject.opts._type === CHART_TYPE.SCATTER) {
  279. /* UNUSED:
  280. strSheetXml += '<cols>'
  281. strSheetXml += '<col min="1" max="' + data.length + '" width="11" customWidth="1" />'
  282. //data.forEach((obj,idx)=>{ strSheetXml += '<col min="'+(idx+1)+'" max="'+(idx+1)+'" width="11" customWidth="1" />' });
  283. strSheetXml += '</cols>'
  284. */
  285. /* EX: INPUT: `data`
  286. [
  287. { name:'X-AxisA', values:[ 1, 2, 3, 4, 5] },
  288. { name:'Y-AxisB', values:[ 2,22,42,52,62] },
  289. { name:'Y-AxisC', values:[ 3,33,43,53,63] }
  290. ];
  291. */
  292. /* EX: OUTPUT: sheet1.xml:
  293. -|----A----|----B----|----C----|
  294. 1| X-AxisA | Y-AxisB | Y-AxisC |
  295. 2| 1 | 2 | 3 |
  296. -|---------|---------|---------|
  297. */
  298. strSheetXml += '<sheetData>'
  299. // A: Create header row first (every `name` row provided)
  300. strSheetXml += `<row r="1" spans="1:${data.length}">`
  301. for (let idx = 0; idx < data.length; idx++) {
  302. strSheetXml += `<c r="${getExcelColName(idx + 1)}1" t="s"><v>${idx}</v></c>` // NOTE: add `t="s"` for label cols!
  303. }
  304. strSheetXml += '</row>'
  305. // B: Add row for each X-Axis value (Y-Axis* value is optional)
  306. data[0].values.forEach((val, idx) => {
  307. // Leading col is reserved for the 'X-Axis' value, so hard-code it, then loop over col values
  308. strSheetXml += `<row r="${idx + 2}" spans="1:${data.length}">`
  309. strSheetXml += `<c r="A${idx + 2}"><v>${val}</v></c>`
  310. // Add Y-Axis 1->N
  311. for (let idy = 1; idy < data.length; idy++) {
  312. strSheetXml += `<c r="${getExcelColName(idy + 1)}${idx + 2}"><v>${data[idy].values[idx] || data[idy].values[idx] === 0 ? data[idy].values[idx] : ''
  313. }</v></c>`
  314. }
  315. strSheetXml += '</row>'
  316. })
  317. } else {
  318. // strSheetXml += '<cols><col min="1" max="1" width="11" customWidth="1" /></cols>'
  319. strSheetXml += '<sheetData>'
  320. /* EX: INPUT: `data`
  321. [
  322. { name:'Red', labels:['Jan..May-17'], values:[11,13,14,15,16] },
  323. { name:'Amb', labels:['Jan..May-17'], values:[22, 6, 7, 8, 9] },
  324. { name:'Grn', labels:['Jan..May-17'], values:[33,32,42,53,63] }
  325. ];
  326. */
  327. /* EX: OUTPUT: lineChart Worksheet:
  328. -|---A---|--B--|--C--|--D--|
  329. 1| | Red | Amb | Grn |
  330. 2|Jan-17 | 11| 22| 33|
  331. 3|Feb-17 | 55| 43| 70|
  332. 4|Mar-17 | 56| 143| 99|
  333. 5|Apr-17 | 65| 3| 120|
  334. 6|May-17 | 75| 93| 170|
  335. -|-------|-----|-----|-----|
  336. */
  337. if (!IS_MULTI_CAT_AXES) {
  338. // A: Create header row first
  339. strSheetXml += `<row r="1" spans="1:${data.length + data[0].labels.length}">`
  340. data[0].labels.forEach((_labelsGroup, idx) => {
  341. strSheetXml += `<c r="${getExcelColName(idx + 1)}1" t="s"><v>0</v></c>`
  342. })
  343. for (let idx = 0; idx < data.length; idx++) {
  344. strSheetXml += `<c r="${getExcelColName(idx + 1 + data[0].labels.length)}1" t="s"><v>${idx + 1}</v></c>` // NOTE: use `t="s"` for label cols!
  345. }
  346. strSheetXml += '</row>'
  347. // B: Add data row(s) for each category
  348. data[0].labels[0].forEach((_cat, idx) => {
  349. strSheetXml += `<row r="${idx + 2}" spans="1:${data.length + data[0].labels.length}">`
  350. // Leading cols are reserved for the label groups
  351. for (let idx2 = data[0].labels.length - 1; idx2 >= 0; idx2--) {
  352. strSheetXml += `<c r="${getExcelColName(data[0].labels.length - idx2)}${idx + 2}" t="s">`
  353. strSheetXml += `<v>${data.length + idx + 1}</v>`
  354. strSheetXml += '</c>'
  355. }
  356. for (let idy = 0; idy < data.length; idy++) {
  357. strSheetXml += `<c r="${getExcelColName(data[0].labels.length + idy + 1)}${idx + 2}"><v>${data[idy].values[idx] || ''}</v></c>`
  358. }
  359. strSheetXml += '</row>'
  360. })
  361. } else {
  362. // A: create header row
  363. strSheetXml += `<row r="1" spans="1:${data.length + data[0].labels.length}">`
  364. for (let idx = 0; idx < data[0].labels.length; idx++) {
  365. strSheetXml += `<c r="${getExcelColName(idx + 1)}1" t="s"><v>0</v></c>`
  366. }
  367. for (let idx = data[0].labels.length - 1; idx < data.length + data[0].labels.length - 1; idx++) {
  368. strSheetXml += `<c r="${getExcelColName(idx + data[0].labels.length)}1" t="s"><v>${idx}</v></c>` // NOTE: use `t="s"` for label cols!
  369. }
  370. strSheetXml += '</row>'
  371. // FIXME: 20220524 (v3.11.0)
  372. /**
  373. * @example INPUT
  374. * const LABELS = [
  375. * ["Gear", "Berg", "Motr", "Swch", "Plug", "Cord", "Pump", "Leak", "Seal"],
  376. * ["Mech", "", "", "Elec", "", "", "Hydr", "", ""],
  377. * ];
  378. * const arrDataRegions = [
  379. * { name: "West", labels: LABELS, values: [11, 8, 3, 0, 11, 3, 0, 0, 0] },
  380. * { name: "Ctrl", labels: LABELS, values: [0, 11, 6, 19, 12, 5, 0, 0, 0] },
  381. * { name: "East", labels: LABELS, values: [0, 3, 2, 0, 0, 0, 4, 3, 1] },
  382. * ];
  383. */
  384. /**
  385. * @example OUTPUT EXCEL SHEET
  386. * |/|---A--|---B--|---C--|---D--|---E--|
  387. * |1| | | West | Ctrl | East |
  388. * |2| Mech | Gear | ## | ## | ## |
  389. * |3| | Brng | ## | ## | ## |
  390. * |4| | Motr | ## | ## | ## |
  391. * |5| Elec | Swch | ## | ## | ## |
  392. * |6| | Plug | ## | ## | ## |
  393. * |7| | Cord | ## | ## | ## |
  394. * |8| Hydr | Pump | ## | ## | ## |
  395. * |9| | Leak | ## | ## | ## |
  396. *|10| | Seal | ## | ## | ## |
  397. */
  398. /**
  399. * @example OUTPUT EXCEL SHEET XML
  400. * <row r="1" spans="1:5">
  401. * <c r="A1" t="s"><v>0</v></c>
  402. * <c r="B1" t="s"><v>0</v></c>
  403. * <c r="C1" t="s"><v>1</v></c>
  404. * <c r="D1" t="s"><v>2</v></c>
  405. * <c r="E1" t="s"><v>3</v></c>
  406. * </row>
  407. * <row r="2" spans="1:5">
  408. * <c r="A2" t="s"><v>4</v></c>
  409. * <c r="B2" t="s"><v>7</v></c>
  410. * <c r="C2" ><v>###</v></c>
  411. * </row>
  412. * <row r="3" spans="1:5">
  413. * <c r="A3" />
  414. * <c r="B3" t="s"><v>8</v></c>
  415. * <c r="C3" ><v>###</v></c>
  416. * </row>
  417. */
  418. /**
  419. * @example SHARED-STRINGS
  420. * 1=West, 2=Ctrl, 3=East, 4=Mech, 5=Elec, 6=Mydr, 7=Gear, 8=Brng, [...], 15=Seal
  421. */
  422. // B: Add data row(s) for each category
  423. /**
  424. * const LABELS = [
  425. * ["Gear", "Berg", "Motr", "Swch", "Plug", "Cord", "Pump", "Leak", "Seal"],
  426. * ["Mech", "", "", "Elec", "", "", "Hydr", "", ""],
  427. * ["2010", "", "", "", "", "", "", "", ""],
  428. * ];
  429. */
  430. const TOT_SER = data.length
  431. const TOT_CAT = data[0].labels[0].length
  432. const TOT_LVL = data[0].labels.length
  433. // Iterate across labels/cats as these are the <row>'s
  434. for (let idx = 0; idx < TOT_CAT; idx++) {
  435. // A: start row
  436. strSheetXml += `<row r="${idx + 2}" spans="1:${TOT_SER + TOT_LVL}">`
  437. // WIP: FIXME:
  438. // B: add a col for each label/cat
  439. let totLabels = TOT_SER
  440. const revLabelGroups = data[0].labels.slice().reverse()
  441. revLabelGroups.forEach((labelsGroup, idy) => {
  442. /**
  443. * const LABELS_REVERSED = [
  444. * ["Mech", "", "", "Elec", "", "", "Hydr", "", ""],
  445. * ["Gear", "Berg", "Motr", "Swch", "Plug", "Cord", "Pump", "Leak", "Seal"],
  446. * ];
  447. */
  448. const colLabel = labelsGroup[idx]
  449. if (colLabel) {
  450. const totGrpLbls = idy === 0 ? 1 : revLabelGroups[idy - 1].filter(label => label && label !== '').length // get unique label so we can add to get proper shared-string #
  451. totLabels += totGrpLbls
  452. strSheetXml += `<c r="${getExcelColName(idx + 1 + idy)}${idx + 2}" t="s"><v>${totLabels}</v></c>`
  453. }
  454. })
  455. // WIP: FIXME:
  456. // C: add a col for each data value
  457. for (let idy = 0; idy < TOT_SER; idy++) {
  458. strSheetXml += `<c r="${getExcelColName(TOT_LVL + idy + 1)}${idx + 2}"><v>${data[idy].values[idx] || 0}</v></c>`
  459. }
  460. // D: Done
  461. strSheetXml += '</row>'
  462. }
  463. // console.log(strSheetXml) // WIP: CHECK:
  464. // console.log(`---CHECK ABOVE---------------------`)
  465. }
  466. }
  467. strSheetXml += '</sheetData>'
  468. /* FIXME: support multi-level
  469. if (IS_MULTI_CAT_AXES) {
  470. strSheetXml += '<mergeCells count="3">'
  471. strSheetXml += ' <mergeCell ref="A2:A4"/>'
  472. strSheetXml += ' <mergeCell ref="A10:A12"/>'
  473. strSheetXml += ' <mergeCell ref="A5:A9"/>'
  474. strSheetXml += '</mergeCells>'
  475. }
  476. */
  477. strSheetXml += '<pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>'
  478. // Link the `table1.xml` file to define an actual Table in Excel
  479. // NOTE: This only works with scatter charts - all others give a "cannot find linked file" error
  480. // ....: Since we dont need the table anyway (chart data can be edited/range selected, etc.), just dont use this
  481. // ....: Leaving this so nobody foolishly attempts to add this in the future
  482. // strSheetXml += '<tableParts count="1"><tablePart r:id="rId1"/></tableParts>'
  483. strSheetXml += '</worksheet>\n'
  484. zipExcel.file('xl/worksheets/sheet1.xml', strSheetXml)
  485. }
  486. // C: Add XLSX to PPTX export
  487. zipExcel
  488. .generateAsync({ type: 'base64' })
  489. .then(content => {
  490. // 1: Create the embedded Excel worksheet with labels and data
  491. zip.file(`ppt/embeddings/Microsoft_Excel_Worksheet${chartObject.globalId}.xlsx`, content, { base64: true })
  492. // 2: Create the chart.xml and rel files
  493. zip.file(
  494. 'ppt/charts/_rels/' + chartObject.fileName + '.rels',
  495. '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
  496. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
  497. `<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/package" Target="../embeddings/Microsoft_Excel_Worksheet${chartObject.globalId}.xlsx"/>` +
  498. '</Relationships>'
  499. )
  500. zip.file(`ppt/charts/${chartObject.fileName}`, makeXmlCharts(chartObject))
  501. // 3: Done
  502. resolve('')
  503. })
  504. .catch(strErr => {
  505. reject(strErr)
  506. })
  507. })
  508. }
  509. /**
  510. * Main entry point method for create charts
  511. * @see: http://www.datypic.com/sc/ooxml/s-dml-chart.xsd.html
  512. * @param {ISlideRelChart} rel - chart object
  513. * @return {string} XML
  514. */
  515. export function makeXmlCharts (rel: ISlideRelChart): string {
  516. let strXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
  517. let usesSecondaryValAxis = false
  518. // STEP 1: Create chart
  519. {
  520. // CHARTSPACE: BEGIN vvv
  521. strXml +=
  522. '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
  523. strXml += '<c:date1904 val="0"/>' // ppt defaults to 1904 dates, excel to 1900
  524. strXml += `<c:roundedCorners val="${rel.opts.chartArea.roundedCorners ? '1' : '0'}"/>`
  525. strXml += '<c:chart>'
  526. // OPTION: Title
  527. if (rel.opts.showTitle) {
  528. strXml += genXmlTitle(
  529. {
  530. title: rel.opts.title || 'Chart Title',
  531. color: rel.opts.titleColor,
  532. fontFace: rel.opts.titleFontFace,
  533. fontSize: rel.opts.titleFontSize || DEF_FONT_TITLE_SIZE,
  534. titleAlign: rel.opts.titleAlign,
  535. titleBold: rel.opts.titleBold,
  536. titlePos: rel.opts.titlePos,
  537. titleRotate: rel.opts.titleRotate,
  538. },
  539. rel.opts.x as number,
  540. rel.opts.y as number
  541. )
  542. strXml += '<c:autoTitleDeleted val="0"/>'
  543. } else {
  544. // NOTE: Add autoTitleDeleted tag in else to prevent default creation of chart title even when showTitle is set to false
  545. strXml += '<c:autoTitleDeleted val="1"/>'
  546. }
  547. /** Add 3D view tag
  548. * @see: https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_perspective_topic_ID0E6BUQB.html
  549. */
  550. if (rel.opts._type === CHART_TYPE.BAR3D) {
  551. strXml += `<c:view3D><c:rotX val="${rel.opts.v3DRotX}"/><c:rotY val="${rel.opts.v3DRotY}"/><c:rAngAx val="${!rel.opts.v3DRAngAx ? 0 : 1}"/><c:perspective val="${rel.opts.v3DPerspective}"/></c:view3D>`
  552. }
  553. strXml += '<c:plotArea>'
  554. // IMPORTANT: Dont specify layout to enable auto-fit: PPT does a great job maximizing space with all 4 TRBL locations
  555. if (rel.opts.layout) {
  556. strXml += '<c:layout>'
  557. strXml += ' <c:manualLayout>'
  558. strXml += ' <c:layoutTarget val="inner" />'
  559. strXml += ' <c:xMode val="edge" />'
  560. strXml += ' <c:yMode val="edge" />'
  561. strXml += ' <c:x val="' + (rel.opts.layout.x || 0) + '" />'
  562. strXml += ' <c:y val="' + (rel.opts.layout.y || 0) + '" />'
  563. strXml += ' <c:w val="' + (rel.opts.layout.w || 1) + '" />'
  564. strXml += ' <c:h val="' + (rel.opts.layout.h || 1) + '" />'
  565. strXml += ' </c:manualLayout>'
  566. strXml += '</c:layout>'
  567. } else {
  568. strXml += '<c:layout/>'
  569. }
  570. }
  571. // A: Create Chart XML -----------------------------------------------------------
  572. if (Array.isArray(rel.opts._type)) {
  573. rel.opts._type.forEach(type => {
  574. // TODO: FIXME: theres `options` on chart rels??
  575. const options = { ...rel.opts, ...type.options }
  576. // let options: IChartOptsLib = { type: type.type, }
  577. const valAxisId = options.secondaryValAxis ? AXIS_ID_VALUE_SECONDARY : AXIS_ID_VALUE_PRIMARY
  578. const catAxisId = options.secondaryCatAxis ? AXIS_ID_CATEGORY_SECONDARY : AXIS_ID_CATEGORY_PRIMARY
  579. usesSecondaryValAxis = usesSecondaryValAxis || options.secondaryValAxis
  580. strXml += makeChartType(type.type, type.data, options, valAxisId, catAxisId, true)
  581. })
  582. } else {
  583. strXml += makeChartType(rel.opts._type, rel.data, rel.opts, AXIS_ID_VALUE_PRIMARY, AXIS_ID_CATEGORY_PRIMARY, false)
  584. }
  585. // B: Axes -----------------------------------------------------------
  586. if (rel.opts._type !== CHART_TYPE.PIE && rel.opts._type !== CHART_TYPE.DOUGHNUT) {
  587. // Param check
  588. if (rel.opts.valAxes && rel.opts.valAxes.length > 1 && !usesSecondaryValAxis) {
  589. throw new Error('Secondary axis must be used by one of the multiple charts')
  590. }
  591. if (rel.opts.catAxes) {
  592. if (!rel.opts.valAxes || rel.opts.valAxes.length !== rel.opts.catAxes.length) {
  593. throw new Error('There must be the same number of value and category axes.')
  594. }
  595. strXml += makeCatAxis({ ...rel.opts, ...rel.opts.catAxes[0] }, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY)
  596. } else {
  597. strXml += makeCatAxis(rel.opts, AXIS_ID_CATEGORY_PRIMARY, AXIS_ID_VALUE_PRIMARY)
  598. }
  599. if (rel.opts.valAxes) {
  600. strXml += makeValAxis({ ...rel.opts, ...rel.opts.valAxes[0] }, AXIS_ID_VALUE_PRIMARY)
  601. if (rel.opts.valAxes[1]) {
  602. strXml += makeValAxis({ ...rel.opts, ...rel.opts.valAxes[1] }, AXIS_ID_VALUE_SECONDARY)
  603. }
  604. } else {
  605. strXml += makeValAxis(rel.opts, AXIS_ID_VALUE_PRIMARY)
  606. // Add series axis for 3D bar
  607. if (rel.opts._type === CHART_TYPE.BAR3D) {
  608. strXml += makeSerAxis(rel.opts, AXIS_ID_SERIES_PRIMARY, AXIS_ID_VALUE_PRIMARY)
  609. }
  610. }
  611. // Combo Charts: Add secondary axes after all vals
  612. if (rel.opts?.catAxes && rel.opts?.catAxes[1]) {
  613. strXml += makeCatAxis({ ...rel.opts, ...rel.opts.catAxes[1] }, AXIS_ID_CATEGORY_SECONDARY, AXIS_ID_VALUE_SECONDARY)
  614. }
  615. }
  616. // C: Chart Properties and plotArea Options: Border, Data Table, Fill, Legend
  617. {
  618. // NOTE: DataTable goes between '</c:valAx>' and '<c:spPr>'
  619. if (rel.opts.showDataTable) {
  620. strXml += '<c:dTable>'
  621. strXml += ` <c:showHorzBorder val="${!rel.opts.showDataTableHorzBorder ? 0 : 1}"/>`
  622. strXml += ` <c:showVertBorder val="${!rel.opts.showDataTableVertBorder ? 0 : 1}"/>`
  623. strXml += ` <c:showOutline val="${!rel.opts.showDataTableOutline ? 0 : 1}"/>`
  624. strXml += ` <c:showKeys val="${!rel.opts.showDataTableKeys ? 0 : 1}"/>`
  625. strXml += ' <c:spPr>'
  626. strXml += ' <a:noFill/>'
  627. strXml += ' <a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="tx1"><a:lumMod val="15000"/><a:lumOff val="85000"/></a:schemeClr></a:solidFill><a:round/></a:ln>'
  628. strXml += ' <a:effectLst/>'
  629. strXml += ' </c:spPr>'
  630. strXml += ' <c:txPr>'
  631. strXml += ' <a:bodyPr rot="0" spcFirstLastPara="1" vertOverflow="ellipsis" vert="horz" wrap="square" anchor="ctr" anchorCtr="1"/>'
  632. strXml += ' <a:lstStyle/>'
  633. strXml += ' <a:p>'
  634. strXml += ' <a:pPr rtl="0">'
  635. strXml += ` <a:defRPr sz="${Math.round((rel.opts.dataTableFontSize || DEF_FONT_SIZE) * 100)}" b="0" i="0" u="none" strike="noStrike" kern="1200" baseline="0">`
  636. strXml += ' <a:solidFill><a:schemeClr val="tx1"><a:lumMod val="65000"/><a:lumOff val="35000"/></a:schemeClr></a:solidFill>'
  637. strXml += ' <a:latin typeface="+mn-lt"/>'
  638. strXml += ' <a:ea typeface="+mn-ea"/>'
  639. strXml += ' <a:cs typeface="+mn-cs"/>'
  640. strXml += ' </a:defRPr>'
  641. strXml += ' </a:pPr>'
  642. strXml += ' <a:endParaRPr lang="en-US"/>'
  643. strXml += ' </a:p>'
  644. strXml += ' </c:txPr>'
  645. strXml += '</c:dTable>'
  646. }
  647. strXml += ' <c:spPr>'
  648. // OPTION: Fill
  649. strXml += rel.opts.plotArea.fill?.color ? genXmlColorSelection(rel.opts.plotArea.fill) : '<a:noFill/>'
  650. // OPTION: Border
  651. strXml += rel.opts.plotArea.border
  652. ? `<a:ln w="${valToPts(rel.opts.plotArea.border.pt)}" cap="flat">${genXmlColorSelection(rel.opts.plotArea.border.color)}</a:ln>`
  653. : '<a:ln><a:noFill/></a:ln>'
  654. // Close shapeProp/plotArea before Legend
  655. strXml += ' <a:effectLst/>'
  656. strXml += ' </c:spPr>'
  657. strXml += '</c:plotArea>'
  658. // OPTION: Legend
  659. // IMPORTANT: Dont specify layout to enable auto-fit: PPT does a great job maximizing space with all 4 TRBL locations
  660. if (rel.opts.showLegend) {
  661. strXml += '<c:legend>'
  662. strXml += '<c:legendPos val="' + rel.opts.legendPos + '"/>'
  663. // strXml += '<c:layout/>'
  664. strXml += '<c:overlay val="0"/>'
  665. if (rel.opts.legendFontFace || rel.opts.legendFontSize || rel.opts.legendColor) {
  666. strXml += '<c:txPr>'
  667. strXml += ' <a:bodyPr/>'
  668. strXml += ' <a:lstStyle/>'
  669. strXml += ' <a:p>'
  670. strXml += ' <a:pPr>'
  671. strXml += rel.opts.legendFontSize ? `<a:defRPr sz="${Math.round(Number(rel.opts.legendFontSize) * 100)}">` : '<a:defRPr>'
  672. if (rel.opts.legendColor) strXml += genXmlColorSelection(rel.opts.legendColor)
  673. if (rel.opts.legendFontFace) strXml += '<a:latin typeface="' + rel.opts.legendFontFace + '"/>'
  674. if (rel.opts.legendFontFace) strXml += '<a:cs typeface="' + rel.opts.legendFontFace + '"/>'
  675. strXml += ' </a:defRPr>'
  676. strXml += ' </a:pPr>'
  677. strXml += ' <a:endParaRPr lang="en-US"/>'
  678. strXml += ' </a:p>'
  679. strXml += '</c:txPr>'
  680. }
  681. strXml += '</c:legend>'
  682. }
  683. }
  684. strXml += ' <c:plotVisOnly val="1"/>'
  685. strXml += ' <c:dispBlanksAs val="' + rel.opts.displayBlanksAs + '"/>'
  686. if (rel.opts._type === CHART_TYPE.SCATTER) strXml += '<c:showDLblsOverMax val="1"/>'
  687. strXml += '</c:chart>'
  688. // D: CHARTSPACE SHAPE PROPS
  689. strXml += '<c:spPr>'
  690. strXml += rel.opts.chartArea.fill?.color ? genXmlColorSelection(rel.opts.chartArea.fill) : '<a:noFill/>'
  691. strXml += rel.opts.chartArea.border
  692. ? `<a:ln w="${valToPts(rel.opts.chartArea.border.pt)}" cap="flat">${genXmlColorSelection(rel.opts.chartArea.border.color)}</a:ln>`
  693. : '<a:ln><a:noFill/></a:ln>'
  694. strXml += ' <a:effectLst/>'
  695. strXml += '</c:spPr>'
  696. // E: DATA (Add relID)
  697. strXml += '<c:externalData r:id="rId1"><c:autoUpdate val="0"/></c:externalData>'
  698. // LAST: chartSpace end
  699. strXml += '</c:chartSpace>'
  700. return strXml
  701. }
  702. /**
  703. * Create XML string for any given chart type
  704. * @param {CHART_NAME} chartType chart type name
  705. * @param {IOptsChartData[]} data chart data
  706. * @param {IChartOptsLib} opts chart options
  707. * @param {string} valAxisId chart val axis id
  708. * @param {string} catAxisId chart cat axis id
  709. * @param {boolean} isMultiTypeChart is this a mutli-type chart?
  710. * @example 'bubble' returns <c:bubbleChart></c>
  711. * @example '<c:lineChart>'
  712. * @return {string} XML chart
  713. */
  714. function makeChartType (chartType: CHART_NAME, data: IOptsChartData[], opts: IChartOptsLib, valAxisId: string, catAxisId: string, isMultiTypeChart: boolean): string {
  715. // NOTE: "Chart Range" (as shown in "select Chart Area dialog") is calculated.
  716. // ....: Ensure each X/Y Axis/Col has same row height (esp. applicable to XY Scatter where X can often be larger than Y's)
  717. let colorIndex = -1 // Maintain the color index by region
  718. let idxColLtr = 1
  719. let optsChartData: IOptsChartData = null
  720. let strXml = ''
  721. switch (chartType) {
  722. case CHART_TYPE.AREA:
  723. case CHART_TYPE.BAR:
  724. case CHART_TYPE.BAR3D:
  725. case CHART_TYPE.LINE:
  726. case CHART_TYPE.RADAR:
  727. // 1: Start Chart
  728. strXml += `<c:${chartType}Chart>`
  729. if (chartType === CHART_TYPE.AREA && opts.barGrouping === 'stacked') {
  730. strXml += '<c:grouping val="' + opts.barGrouping + '"/>'
  731. }
  732. if (chartType === CHART_TYPE.BAR || chartType === CHART_TYPE.BAR3D) {
  733. strXml += '<c:barDir val="' + opts.barDir + '"/>'
  734. strXml += '<c:grouping val="' + (opts.barGrouping || 'clustered') + '"/>'
  735. }
  736. if (chartType === CHART_TYPE.RADAR) {
  737. strXml += '<c:radarStyle val="' + opts.radarStyle + '"/>'
  738. }
  739. strXml += '<c:varyColors val="0"/>'
  740. // 2: "Series" block for every data row
  741. /* EX1:
  742. data: [
  743. {
  744. name: 'Region 1',
  745. labels: [['April', 'May', 'June', 'July']],
  746. values: [17, 26, 53, 96]
  747. },
  748. {
  749. name: 'Region 2',
  750. labels: [['April', 'May', 'June', 'July']],
  751. values: [55, 43, 70, 58]
  752. }
  753. ]
  754. */
  755. /* EX2:
  756. data: [
  757. {
  758. name: 'Region 1',
  759. labels: [
  760. ['April', 'May', 'June', 'April', 'May', 'June'],
  761. ['2020', '', '', '2021', '', '']
  762. ],
  763. values: [17, 26, 53, 96, 40, 33]
  764. },
  765. {
  766. name: 'Region 2',
  767. labels: [
  768. ['April', 'May', 'June', 'April', 'May', 'June'],
  769. ['2020', '', '', '2021', '', '']
  770. ],
  771. values: [55, 43, 70, 58, 78, 63]
  772. }
  773. ]
  774. */
  775. data.forEach(obj => {
  776. colorIndex++
  777. strXml += '<c:ser>'
  778. strXml += ` <c:idx val="${obj._dataIndex}"/><c:order val="${obj._dataIndex}"/>`
  779. strXml += ' <c:tx>'
  780. strXml += ' <c:strRef>'
  781. strXml += ' <c:f>Sheet1!$' + getExcelColName(obj._dataIndex + obj.labels.length + 1) + '$1</c:f>'
  782. strXml += ' <c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>' + encodeXmlEntities(obj.name) + '</c:v></c:pt></c:strCache>'
  783. strXml += ' </c:strRef>'
  784. strXml += ' </c:tx>'
  785. // Fill and Border
  786. // TODO: CURRENT: Pull#727
  787. // TODO: let seriesColor = obj.color ? obj.color : opts.chartColors ? opts.chartColors[colorIndex % opts.chartColors.length] : null
  788. const seriesColor = opts.chartColors ? opts.chartColors[colorIndex % opts.chartColors.length] : null
  789. strXml += ' <c:spPr>'
  790. if (seriesColor === 'transparent') {
  791. strXml += '<a:noFill/>'
  792. } else if (opts.chartColorsOpacity) {
  793. strXml += '<a:solidFill>' + createColorElement(seriesColor, `<a:alpha val="${Math.round(opts.chartColorsOpacity * 1000)}"/>`) + '</a:solidFill>'
  794. } else {
  795. strXml += '<a:solidFill>' + createColorElement(seriesColor) + '</a:solidFill>'
  796. }
  797. if (chartType === CHART_TYPE.LINE || chartType === CHART_TYPE.RADAR) {
  798. if (opts.lineSize === 0) {
  799. strXml += '<a:ln><a:noFill/></a:ln>'
  800. } else {
  801. strXml += `<a:ln w="${valToPts(opts.lineSize)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(seriesColor)}</a:solidFill>`
  802. strXml += '<a:prstDash val="' + (opts.lineDash || 'solid') + '"/><a:round/></a:ln>'
  803. }
  804. } else if (opts.dataBorder) {
  805. strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`
  806. }
  807. strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW)
  808. strXml += ' </c:spPr>'
  809. strXml += ' <c:invertIfNegative val="0"/>'
  810. // Data Labels per series
  811. // NOTE: [20190117] Adding these to RADAR chart causes unrecoverable corruption!
  812. if (chartType !== CHART_TYPE.RADAR) {
  813. strXml += '<c:dLbls>'
  814. strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || 'General'}" sourceLinked="0"/>`
  815. if (opts.dataLabelBkgrdColors) strXml += `<c:spPr><a:solidFill>${createColorElement(seriesColor)}</a:solidFill></c:spPr>`
  816. strXml += '<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>'
  817. strXml += `<a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(
  818. (opts.dataLabelFontSize || DEF_FONT_SIZE) * 100
  819. )}" u="none">`
  820. strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || DEF_FONT_COLOR)}</a:solidFill>`
  821. strXml += `<a:latin typeface="${opts.dataLabelFontFace || 'Arial'}"/>`
  822. strXml += '</a:defRPr></a:pPr></a:p></c:txPr>'
  823. if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`
  824. strXml += '<c:showLegendKey val="0"/>'
  825. strXml += `<c:showVal val="${opts.showValue ? '1' : '0'}"/>`
  826. strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? '1' : '0'}"/><c:showPercent val="0"/><c:showBubbleSize val="0"/>`
  827. strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? '1' : '0'}"/>`
  828. strXml += '</c:dLbls>'
  829. }
  830. // 'c:marker' tag: `lineDataSymbol`
  831. if (chartType === CHART_TYPE.LINE || chartType === CHART_TYPE.RADAR) {
  832. strXml += '<c:marker>'
  833. strXml += ' <c:symbol val="' + opts.lineDataSymbol + '"/>'
  834. if (opts.lineDataSymbolSize) strXml += `<c:size val="${opts.lineDataSymbolSize}"/>` // Defaults to "auto" otherwise (but this is usually too small, so there is a default)
  835. strXml += ' <c:spPr>'
  836. strXml += ` <a:solidFill>${createColorElement(opts.chartColors[obj._dataIndex + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : obj._dataIndex])}</a:solidFill>`
  837. strXml += ` <a:ln w="${opts.lineDataSymbolLineSize}" cap="flat"><a:solidFill>${createColorElement(opts.lineDataSymbolLineColor || seriesColor)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`
  838. strXml += ' <a:effectLst/>'
  839. strXml += ' </c:spPr>'
  840. strXml += '</c:marker>'
  841. }
  842. // Allow users with a single data set to pass their own array of colors (check for this using != ours)
  843. // Color chart bars various colors when >1 color
  844. // NOTE: `<c:dPt>` created with various colors will change PPT legend by design so each dataPt/color is an legend item!
  845. if (
  846. (chartType === CHART_TYPE.BAR || chartType === CHART_TYPE.BAR3D) &&
  847. data.length === 1 &&
  848. ((opts.chartColors && opts.chartColors !== BARCHART_COLORS && opts.chartColors.length > 1) || (opts.invertedColors?.length))
  849. ) {
  850. // Series Data Point colors
  851. obj.values.forEach((value, index) => {
  852. const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || []
  853. strXml += ' <c:dPt>'
  854. strXml += ` <c:idx val="${index}"/>`
  855. strXml += ' <c:invertIfNegative val="0"/>'
  856. strXml += ' <c:bubble3D val="0"/>'
  857. strXml += ' <c:spPr>'
  858. if (opts.lineSize === 0) {
  859. strXml += '<a:ln><a:noFill/></a:ln>'
  860. } else if (chartType === CHART_TYPE.BAR) {
  861. strXml += '<a:solidFill>'
  862. strXml += ' <a:srgbClr val="' + arrColors[index % arrColors.length] + '"/>'
  863. strXml += '</a:solidFill>'
  864. } else {
  865. strXml += '<a:ln>'
  866. strXml += ' <a:solidFill>'
  867. strXml += ' <a:srgbClr val="' + arrColors[index % arrColors.length] + '"/>'
  868. strXml += ' </a:solidFill>'
  869. strXml += '</a:ln>'
  870. }
  871. strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW)
  872. strXml += ' </c:spPr>'
  873. strXml += ' </c:dPt>'
  874. })
  875. }
  876. // 2: "Categories"
  877. {
  878. strXml += '<c:cat>'
  879. if (opts.catLabelFormatCode) {
  880. // Use 'numRef' as catLabelFormatCode implies that we are expecting numbers here
  881. strXml += ' <c:numRef>'
  882. strXml += ` <c:f>Sheet1!$A$2:$A$${obj.labels[0].length + 1}</c:f>`
  883. strXml += ' <c:numCache>'
  884. strXml += ' <c:formatCode>' + (opts.catLabelFormatCode || 'General') + '</c:formatCode>'
  885. strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`
  886. obj.labels[0].forEach((label, idx) => (strXml += `<c:pt idx="${idx}"><c:v>${encodeXmlEntities(label)}</c:v></c:pt>`))
  887. strXml += ' </c:numCache>'
  888. strXml += ' </c:numRef>'
  889. } else {
  890. strXml += ' <c:multiLvlStrRef>'
  891. strXml += ` <c:f>Sheet1!$A$2:$${getExcelColName(obj.labels.length)}$${obj.labels[0].length + 1}</c:f>`
  892. strXml += ' <c:multiLvlStrCache>'
  893. strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`
  894. obj.labels.forEach(labelsGroup => {
  895. strXml += '<c:lvl>'
  896. labelsGroup.forEach((label, idx) => (strXml += `<c:pt idx="${idx}"><c:v>${encodeXmlEntities(label)}</c:v></c:pt>`))
  897. strXml += '</c:lvl>'
  898. })
  899. strXml += ' </c:multiLvlStrCache>'
  900. strXml += ' </c:multiLvlStrRef>'
  901. }
  902. strXml += '</c:cat>'
  903. }
  904. // 3: "Values"
  905. {
  906. strXml += '<c:val>'
  907. strXml += ' <c:numRef>'
  908. strXml += `<c:f>Sheet1!$${getExcelColName(obj._dataIndex + obj.labels.length + 1)}$2:$${getExcelColName(obj._dataIndex + obj.labels.length + 1)}$${obj.labels[0].length + 1}</c:f>`
  909. strXml += ' <c:numCache>'
  910. strXml += ' <c:formatCode>' + (opts.valLabelFormatCode || opts.dataTableFormatCode || 'General') + '</c:formatCode>'
  911. strXml += ` <c:ptCount val="${obj.labels[0].length}"/>`
  912. obj.values.forEach((value, idx) => (strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ''}</c:v></c:pt>`))
  913. strXml += ' </c:numCache>'
  914. strXml += ' </c:numRef>'
  915. strXml += '</c:val>'
  916. }
  917. // Option: `smooth`
  918. if (chartType === CHART_TYPE.LINE) strXml += '<c:smooth val="' + (opts.lineSmooth ? '1' : '0') + '"/>'
  919. // 4: Close "SERIES"
  920. strXml += '</c:ser>'
  921. })
  922. // 3: "Data Labels"
  923. {
  924. strXml += ' <c:dLbls>'
  925. strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || 'General'}" sourceLinked="0"/>`
  926. strXml += ' <c:txPr>'
  927. strXml += ' <a:bodyPr/>'
  928. strXml += ' <a:lstStyle/>'
  929. strXml += ' <a:p><a:pPr>'
  930. strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || DEF_FONT_SIZE) * 100)}" u="none">`
  931. strXml += ' <a:solidFill>' + createColorElement(opts.dataLabelColor || DEF_FONT_COLOR) + '</a:solidFill>'
  932. strXml += ' <a:latin typeface="' + (opts.dataLabelFontFace || 'Arial') + '"/>'
  933. strXml += ' </a:defRPr>'
  934. strXml += ' </a:pPr></a:p>'
  935. strXml += ' </c:txPr>'
  936. if (opts.dataLabelPosition) strXml += ' <c:dLblPos val="' + opts.dataLabelPosition + '"/>'
  937. strXml += ' <c:showLegendKey val="0"/>'
  938. strXml += ' <c:showVal val="' + (opts.showValue ? '1' : '0') + '"/>'
  939. strXml += ' <c:showCatName val="0"/>'
  940. strXml += ' <c:showSerName val="' + (opts.showSerName ? '1' : '0') + '"/>'
  941. strXml += ' <c:showPercent val="0"/>'
  942. strXml += ' <c:showBubbleSize val="0"/>'
  943. strXml += ` <c:showLeaderLines val="${opts.showLeaderLines ? '1' : '0'}"/>`
  944. strXml += ' </c:dLbls>'
  945. }
  946. // 4: Add more chart options (gapWidth, line Marker, etc.)
  947. if (chartType === CHART_TYPE.BAR) {
  948. strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`
  949. strXml += ` <c:overlap val="${(opts.barGrouping || '').includes('tacked') ? 100 : opts.barOverlapPct ? opts.barOverlapPct : 0}"/>`
  950. } else if (chartType === CHART_TYPE.BAR3D) {
  951. strXml += ` <c:gapWidth val="${opts.barGapWidthPct}"/>`
  952. strXml += ` <c:gapDepth val="${opts.barGapDepthPct}"/>`
  953. strXml += ' <c:shape val="' + opts.bar3DShape + '"/>'
  954. } else if (chartType === CHART_TYPE.LINE) {
  955. strXml += ' <c:marker val="1"/>'
  956. }
  957. // 5: Add axisId (NOTE: order matters! (category comes first))
  958. strXml += `<c:axId val="${catAxisId}"/><c:axId val="${valAxisId}"/><c:axId val="${AXIS_ID_SERIES_PRIMARY}"/>`
  959. // 6: Close Chart tag
  960. strXml += `</c:${chartType}Chart>`
  961. // end switch
  962. break
  963. case CHART_TYPE.SCATTER:
  964. /*
  965. `data` = [
  966. { name:'X-Axis', values:[1,2,3,4,5,6,7,8,9,10,11,12] },
  967. { name:'Y-Value 1', values:[13, 20, 21, 25] },
  968. { name:'Y-Value 2', values:[ 1, 2, 5, 9] }
  969. ];
  970. */
  971. // 1: Start Chart
  972. strXml += '<c:' + chartType + 'Chart>'
  973. strXml += '<c:scatterStyle val="lineMarker"/>'
  974. strXml += '<c:varyColors val="0"/>'
  975. // 2: Series: (One for each Y-Axis)
  976. colorIndex = -1
  977. data.filter((_obj, idx) => idx > 0).forEach((obj, idx) => {
  978. colorIndex++
  979. strXml += '<c:ser>'
  980. strXml += ` <c:idx val="${idx}"/>`
  981. strXml += ` <c:order val="${idx}"/>`
  982. strXml += ' <c:tx>'
  983. strXml += ' <c:strRef>'
  984. strXml += ` <c:f>Sheet1!$${getExcelColName(idx + 2)}$1</c:f>`
  985. strXml += ' <c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>' + encodeXmlEntities(obj.name) + '</c:v></c:pt></c:strCache>'
  986. strXml += ' </c:strRef>'
  987. strXml += ' </c:tx>'
  988. // 'c:spPr': Fill, Border, Line, LineStyle (dash, etc.), Shadow
  989. strXml += ' <c:spPr>'
  990. {
  991. const tmpSerColor = opts.chartColors[colorIndex % opts.chartColors.length]
  992. if (tmpSerColor === 'transparent') {
  993. strXml += '<a:noFill/>'
  994. } else if (opts.chartColorsOpacity) {
  995. strXml += '<a:solidFill>' + createColorElement(tmpSerColor, '<a:alpha val="' + Math.round(opts.chartColorsOpacity * 1000).toString() + '"/>') + '</a:solidFill>'
  996. } else {
  997. strXml += '<a:solidFill>' + createColorElement(tmpSerColor) + '</a:solidFill>'
  998. }
  999. if (opts.lineSize === 0) {
  1000. strXml += '<a:ln><a:noFill/></a:ln>'
  1001. } else {
  1002. strXml += `<a:ln w="${valToPts(opts.lineSize)}" cap="${createLineCap(opts.lineCap)}"><a:solidFill>${createColorElement(tmpSerColor)}</a:solidFill>`
  1003. strXml += `<a:prstDash val="${opts.lineDash || 'solid'}"/><a:round/></a:ln>`
  1004. }
  1005. // Shadow
  1006. strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW)
  1007. }
  1008. strXml += ' </c:spPr>'
  1009. // 'c:marker' tag: `lineDataSymbol`
  1010. {
  1011. strXml += '<c:marker>'
  1012. strXml += ' <c:symbol val="' + opts.lineDataSymbol + '"/>'
  1013. if (opts.lineDataSymbolSize) {
  1014. // Defaults to "auto" otherwise (but this is usually too small, so there is a default)
  1015. strXml += `<c:size val="${opts.lineDataSymbolSize}"/>`
  1016. }
  1017. strXml += '<c:spPr>'
  1018. strXml += `<a:solidFill>${createColorElement(opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx])}</a:solidFill>`
  1019. strXml += `<a:ln w="${opts.lineDataSymbolLineSize}" cap="flat"><a:solidFill>${createColorElement(opts.lineDataSymbolLineColor || opts.chartColors[colorIndex % opts.chartColors.length])}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`
  1020. strXml += '<a:effectLst/>'
  1021. strXml += '</c:spPr>'
  1022. strXml += '</c:marker>'
  1023. }
  1024. // Option: scatter data point labels
  1025. if (opts.showLabel) {
  1026. const chartUuid = getUuid('-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
  1027. if (obj.labels[0] && (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY')) {
  1028. strXml += '<c:dLbls>'
  1029. obj.labels[0].forEach((label, idx) => {
  1030. if (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY') {
  1031. strXml += ' <c:dLbl>'
  1032. strXml += ` <c:idx val="${idx}"/>`
  1033. strXml += ' <c:tx>'
  1034. strXml += ' <c:rich>'
  1035. strXml += ' <a:bodyPr>'
  1036. strXml += ' <a:spAutoFit/>'
  1037. strXml += ' </a:bodyPr>'
  1038. strXml += ' <a:lstStyle/>'
  1039. strXml += ' <a:p>'
  1040. strXml += ' <a:pPr>'
  1041. strXml += ' <a:defRPr/>'
  1042. strXml += ' </a:pPr>'
  1043. strXml += ' <a:r>'
  1044. strXml += ' <a:rPr lang="' + (opts.lang || 'en-US') + '" dirty="0"/>'
  1045. strXml += ' <a:t>' + encodeXmlEntities(label) + '</a:t>'
  1046. strXml += ' </a:r>'
  1047. // Apply XY values at end of custom label
  1048. // Do not apply the values if the label was empty or just spaces
  1049. // This allows for selective labelling where required
  1050. if (opts.dataLabelFormatScatter === 'customXY' && !/^ *$/.test(label)) {
  1051. strXml += ' <a:r>'
  1052. strXml += ' <a:rPr lang="' + (opts.lang || 'en-US') + '" baseline="0" dirty="0"/>'
  1053. strXml += ' <a:t> (</a:t>'
  1054. strXml += ' </a:r>'
  1055. strXml += ' <a:fld id="{' + getUuid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') + '}" type="XVALUE">'
  1056. strXml += ' <a:rPr lang="' + (opts.lang || 'en-US') + '" baseline="0"/>'
  1057. strXml += ' <a:pPr>'
  1058. strXml += ' <a:defRPr/>'
  1059. strXml += ' </a:pPr>'
  1060. strXml += ' <a:t>[' + encodeXmlEntities(obj.name) + '</a:t>'
  1061. strXml += ' </a:fld>'
  1062. strXml += ' <a:r>'
  1063. strXml += ' <a:rPr lang="' + (opts.lang || 'en-US') + '" baseline="0" dirty="0"/>'
  1064. strXml += ' <a:t>, </a:t>'
  1065. strXml += ' </a:r>'
  1066. strXml += ' <a:fld id="{' + getUuid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') + '}" type="YVALUE">'
  1067. strXml += ' <a:rPr lang="' + (opts.lang || 'en-US') + '" baseline="0"/>'
  1068. strXml += ' <a:pPr>'
  1069. strXml += ' <a:defRPr/>'
  1070. strXml += ' </a:pPr>'
  1071. strXml += ' <a:t>[' + encodeXmlEntities(obj.name) + ']</a:t>'
  1072. strXml += ' </a:fld>'
  1073. strXml += ' <a:r>'
  1074. strXml += ' <a:rPr lang="' + (opts.lang || 'en-US') + '" baseline="0" dirty="0"/>'
  1075. strXml += ' <a:t>)</a:t>'
  1076. strXml += ' </a:r>'
  1077. strXml += ' <a:endParaRPr lang="' + (opts.lang || 'en-US') + '" dirty="0"/>'
  1078. }
  1079. strXml += ' </a:p>'
  1080. strXml += ' </c:rich>'
  1081. strXml += ' </c:tx>'
  1082. strXml += ' <c:spPr>'
  1083. strXml += ' <a:noFill/>'
  1084. strXml += ' <a:ln>'
  1085. strXml += ' <a:noFill/>'
  1086. strXml += ' </a:ln>'
  1087. strXml += ' <a:effectLst/>'
  1088. strXml += ' </c:spPr>'
  1089. if (opts.dataLabelPosition) strXml += ' <c:dLblPos val="' + opts.dataLabelPosition + '"/>'
  1090. strXml += ' <c:showLegendKey val="0"/>'
  1091. strXml += ' <c:showVal val="0"/>'
  1092. strXml += ' <c:showCatName val="0"/>'
  1093. strXml += ' <c:showSerName val="0"/>'
  1094. strXml += ' <c:showPercent val="0"/>'
  1095. strXml += ' <c:showBubbleSize val="0"/>'
  1096. strXml += ' <c:showLeaderLines val="1"/>'
  1097. strXml += ' <c:extLst>'
  1098. strXml += ' <c:ext uri="{CE6537A1-D6FC-4f65-9D91-7224C49458BB}" xmlns:c15="http://schemas.microsoft.com/office/drawing/2012/chart"/>'
  1099. strXml += ' <c:ext uri="{C3380CC4-5D6E-409C-BE32-E72D297353CC}" xmlns:c16="http://schemas.microsoft.com/office/drawing/2014/chart">'
  1100. strXml += ` <c16:uniqueId val="{${'00000000'.substring(0, 8 - (idx + 1).toString().length).toString()}${idx + 1}${chartUuid}}"/>`
  1101. strXml += ' </c:ext>'
  1102. strXml += ' </c:extLst>'
  1103. strXml += '</c:dLbl>'
  1104. }
  1105. })
  1106. strXml += '</c:dLbls>'
  1107. }
  1108. if (opts.dataLabelFormatScatter === 'XY') {
  1109. strXml += '<c:dLbls>'
  1110. strXml += ' <c:spPr>'
  1111. strXml += ' <a:noFill/>'
  1112. strXml += ' <a:ln>'
  1113. strXml += ' <a:noFill/>'
  1114. strXml += ' </a:ln>'
  1115. strXml += ' <a:effectLst/>'
  1116. strXml += ' </c:spPr>'
  1117. strXml += ' <c:txPr>'
  1118. strXml += ' <a:bodyPr>'
  1119. strXml += ' <a:spAutoFit/>'
  1120. strXml += ' </a:bodyPr>'
  1121. strXml += ' <a:lstStyle/>'
  1122. strXml += ' <a:p>'
  1123. strXml += ' <a:pPr>'
  1124. strXml += ' <a:defRPr/>'
  1125. strXml += ' </a:pPr>'
  1126. strXml += ' <a:endParaRPr lang="en-US"/>'
  1127. strXml += ' </a:p>'
  1128. strXml += ' </c:txPr>'
  1129. if (opts.dataLabelPosition) strXml += ' <c:dLblPos val="' + opts.dataLabelPosition + '"/>'
  1130. strXml += ' <c:showLegendKey val="0"/>'
  1131. strXml += ` <c:showVal val="${opts.showLabel ? '1' : '0'}"/>`
  1132. strXml += ` <c:showCatName val="${opts.showLabel ? '1' : '0'}"/>`
  1133. strXml += ` <c:showSerName val="${opts.showSerName ? '1' : '0'}"/>`
  1134. strXml += ' <c:showPercent val="0"/>'
  1135. strXml += ' <c:showBubbleSize val="0"/>'
  1136. strXml += ' <c:extLst>'
  1137. strXml += ' <c:ext uri="{CE6537A1-D6FC-4f65-9D91-7224C49458BB}" xmlns:c15="http://schemas.microsoft.com/office/drawing/2012/chart">'
  1138. strXml += ' <c15:showLeaderLines val="1"/>'
  1139. strXml += ' </c:ext>'
  1140. strXml += ' </c:extLst>'
  1141. strXml += '</c:dLbls>'
  1142. }
  1143. }
  1144. // Color bar chart bars various colors
  1145. // Allow users with a single data set to pass their own array of colors (check for this using != ours)
  1146. if (data.length === 1 && opts.chartColors !== BARCHART_COLORS) {
  1147. // Series Data Point colors
  1148. obj.values.forEach((value, index) => {
  1149. const arrColors = value < 0 ? opts.invertedColors || opts.chartColors || BARCHART_COLORS : opts.chartColors || []
  1150. strXml += ' <c:dPt>'
  1151. strXml += ` <c:idx val="${index}"/>`
  1152. strXml += ' <c:invertIfNegative val="0"/>'
  1153. strXml += ' <c:bubble3D val="0"/>'
  1154. strXml += ' <c:spPr>'
  1155. if (opts.lineSize === 0) {
  1156. strXml += '<a:ln><a:noFill/></a:ln>'
  1157. } else {
  1158. strXml += '<a:solidFill>'
  1159. strXml += ' <a:srgbClr val="' + arrColors[index % arrColors.length] + '"/>'
  1160. strXml += '</a:solidFill>'
  1161. }
  1162. strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW)
  1163. strXml += ' </c:spPr>'
  1164. strXml += ' </c:dPt>'
  1165. })
  1166. }
  1167. // 3: "Values": Scatter Chart has 2: `xVal` and `yVal`
  1168. {
  1169. // X-Axis is always the same
  1170. strXml += '<c:xVal>'
  1171. strXml += ' <c:numRef>'
  1172. strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`
  1173. strXml += ' <c:numCache>'
  1174. strXml += ' <c:formatCode>General</c:formatCode>'
  1175. strXml += ` <c:ptCount val="${data[0].values.length}"/>`
  1176. data[0].values.forEach((value, idx) => {
  1177. strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ''}</c:v></c:pt>`
  1178. })
  1179. strXml += ' </c:numCache>'
  1180. strXml += ' </c:numRef>'
  1181. strXml += '</c:xVal>'
  1182. // Y-Axis vals are this object's `values`
  1183. strXml += '<c:yVal>'
  1184. strXml += ' <c:numRef>'
  1185. strXml += ` <c:f>Sheet1!$${getExcelColName(idx + 2)}$2:$${getExcelColName(idx + 2)}$${data[0].values.length + 1}</c:f>`
  1186. strXml += ' <c:numCache>'
  1187. strXml += ' <c:formatCode>General</c:formatCode>'
  1188. // NOTE: Use pt count and iterate over data[0] (X-Axis) as user can have more values than data (eg: timeline where only first few months are populated)
  1189. strXml += ` <c:ptCount val="${data[0].values.length}"/>`
  1190. data[0].values.forEach((_value, idx) => {
  1191. strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx] || obj.values[idx] === 0 ? obj.values[idx] : ''}</c:v></c:pt>`
  1192. })
  1193. strXml += ' </c:numCache>'
  1194. strXml += ' </c:numRef>'
  1195. strXml += '</c:yVal>'
  1196. }
  1197. // Option: `smooth`
  1198. strXml += '<c:smooth val="' + (opts.lineSmooth ? '1' : '0') + '"/>'
  1199. // 4: Close "SERIES"
  1200. strXml += '</c:ser>'
  1201. })
  1202. // 3: Data Labels
  1203. {
  1204. strXml += ' <c:dLbls>'
  1205. strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || 'General'}" sourceLinked="0"/>`
  1206. strXml += ' <c:txPr>'
  1207. strXml += ' <a:bodyPr/>'
  1208. strXml += ' <a:lstStyle/>'
  1209. strXml += ' <a:p><a:pPr>'
  1210. strXml += ` <a:defRPr b="${opts.dataLabelFontBold ? '1' : '0'}" i="${opts.dataLabelFontItalic ? '1' : '0'}" strike="noStrike" sz="${Math.round((opts.dataLabelFontSize || DEF_FONT_SIZE) * 100)}" u="none">`
  1211. strXml += ' <a:solidFill>' + createColorElement(opts.dataLabelColor || DEF_FONT_COLOR) + '</a:solidFill>'
  1212. strXml += ' <a:latin typeface="' + (opts.dataLabelFontFace || 'Arial') + '"/>'
  1213. strXml += ' </a:defRPr>'
  1214. strXml += ' </a:pPr></a:p>'
  1215. strXml += ' </c:txPr>'
  1216. if (opts.dataLabelPosition) strXml += ' <c:dLblPos val="' + opts.dataLabelPosition + '"/>'
  1217. strXml += ' <c:showLegendKey val="0"/>'
  1218. strXml += ' <c:showVal val="' + (opts.showValue ? '1' : '0') + '"/>'
  1219. strXml += ' <c:showCatName val="0"/>'
  1220. strXml += ' <c:showSerName val="' + (opts.showSerName ? '1' : '0') + '"/>'
  1221. strXml += ' <c:showPercent val="0"/>'
  1222. strXml += ' <c:showBubbleSize val="0"/>'
  1223. strXml += ' </c:dLbls>'
  1224. }
  1225. // 4: Add axis Id (NOTE: order matters! - category comes first)
  1226. strXml += `<c:axId val="${catAxisId}"/><c:axId val="${valAxisId}"/>`
  1227. // 5: Close Chart tag
  1228. strXml += '</c:' + chartType + 'Chart>'
  1229. // end switch
  1230. break
  1231. case CHART_TYPE.BUBBLE:
  1232. case CHART_TYPE.BUBBLE3D:
  1233. /*
  1234. `data` = [
  1235. { name:'X-Axis', values:[1,2,3,4,5,6,7,8,9,10,11,12] },
  1236. { name:'Y-Values 1', values:[13, 20, 21, 25], sizes:[10, 5, 20, 15] },
  1237. { name:'Y-Values 2', values:[ 1, 2, 5, 9], sizes:[ 5, 3, 9, 3] }
  1238. ];
  1239. */
  1240. // 1: Start Chart
  1241. strXml += '<c:bubbleChart>'
  1242. strXml += '<c:varyColors val="0"/>'
  1243. // 2: Series: (One for each Y-Axis)
  1244. colorIndex = -1
  1245. data.filter((_obj, idx) => idx > 0).forEach((obj, idx) => {
  1246. colorIndex++
  1247. strXml += '<c:ser>'
  1248. strXml += ` <c:idx val="${idx}"/>`
  1249. strXml += ` <c:order val="${idx}"/>`
  1250. // A: `<c:tx>`
  1251. strXml += ' <c:tx>'
  1252. strXml += ' <c:strRef>'
  1253. strXml += ' <c:f>Sheet1!$' + getExcelColName(idxColLtr + 1) + '$1</c:f>'
  1254. strXml += ' <c:strCache><c:ptCount val="1"/><c:pt idx="0"><c:v>' + encodeXmlEntities(obj.name) + '</c:v></c:pt></c:strCache>'
  1255. strXml += ' </c:strRef>'
  1256. strXml += ' </c:tx>'
  1257. // B: '<c:spPr>': Fill, Border, Line, LineStyle (dash, etc.), Shadow
  1258. {
  1259. strXml += '<c:spPr>'
  1260. const tmpSerColor = opts.chartColors[colorIndex % opts.chartColors.length]
  1261. if (tmpSerColor === 'transparent') {
  1262. strXml += '<a:noFill/>'
  1263. } else if (opts.chartColorsOpacity) {
  1264. strXml += `<a:solidFill>${createColorElement(tmpSerColor, '<a:alpha val="' + Math.round(opts.chartColorsOpacity * 1000).toString() + '"/>')}</a:solidFill>`
  1265. } else {
  1266. strXml += '<a:solidFill>' + createColorElement(tmpSerColor) + '</a:solidFill>'
  1267. }
  1268. if (opts.lineSize === 0) {
  1269. strXml += '<a:ln><a:noFill/></a:ln>'
  1270. } else if (opts.dataBorder) {
  1271. strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="flat"><a:solidFill>${createColorElement(opts.dataBorder.color)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`
  1272. } else {
  1273. strXml += `<a:ln w="${valToPts(opts.lineSize)}" cap="flat"><a:solidFill>${createColorElement(tmpSerColor)}</a:solidFill>`
  1274. strXml += `<a:prstDash val="${opts.lineDash || 'solid'}"/><a:round/></a:ln>`
  1275. }
  1276. // Shadow
  1277. strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW)
  1278. strXml += '</c:spPr>'
  1279. }
  1280. // C: '<c:dLbls>' "Data Labels"
  1281. // Let it be defaulted for now
  1282. // D: '<c:xVal>'/'<c:yVal>' "Values": Scatter Chart has 2: `xVal` and `yVal`
  1283. {
  1284. // X-Axis is always the same
  1285. strXml += '<c:xVal>'
  1286. strXml += ' <c:numRef>'
  1287. strXml += ` <c:f>Sheet1!$A$2:$A$${data[0].values.length + 1}</c:f>`
  1288. strXml += ' <c:numCache>'
  1289. strXml += ' <c:formatCode>General</c:formatCode>'
  1290. strXml += ` <c:ptCount val="${data[0].values.length}"/>`
  1291. data[0].values.forEach((value, idx) => {
  1292. strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ''}</c:v></c:pt>`
  1293. })
  1294. strXml += ' </c:numCache>'
  1295. strXml += ' </c:numRef>'
  1296. strXml += '</c:xVal>'
  1297. // Y-Axis vals are this object's `values`
  1298. strXml += '<c:yVal>'
  1299. strXml += ' <c:numRef>'
  1300. strXml += `<c:f>Sheet1!$${getExcelColName(idxColLtr + 1)}$2:$${getExcelColName(idxColLtr + 1)}$${data[0].values.length + 1}</c:f>`
  1301. idxColLtr++
  1302. strXml += ' <c:numCache>'
  1303. strXml += ' <c:formatCode>General</c:formatCode>'
  1304. // NOTE: Use pt count and iterate over data[0] (X-Axis) as user can have more values than data (eg: timeline where only first few months are populated)
  1305. strXml += ` <c:ptCount val="${data[0].values.length}"/>`
  1306. data[0].values.forEach((_value, idx) => {
  1307. strXml += `<c:pt idx="${idx}"><c:v>${obj.values[idx] || obj.values[idx] === 0 ? obj.values[idx] : ''}</c:v></c:pt>`
  1308. })
  1309. strXml += ' </c:numCache>'
  1310. strXml += ' </c:numRef>'
  1311. strXml += '</c:yVal>'
  1312. }
  1313. // E: '<c:bubbleSize>'
  1314. strXml += ' <c:bubbleSize>'
  1315. strXml += ' <c:numRef>'
  1316. strXml += `<c:f>Sheet1!$${getExcelColName(idxColLtr + 1)}$2:$${getExcelColName(idxColLtr + 1)}$${obj.sizes.length + 1}</c:f>`
  1317. idxColLtr++
  1318. strXml += ' <c:numCache>'
  1319. strXml += ' <c:formatCode>General</c:formatCode>'
  1320. strXml += ` <c:ptCount val="${obj.sizes.length}"/>`
  1321. obj.sizes.forEach((value, idx) => {
  1322. strXml += `<c:pt idx="${idx}"><c:v>${value || ''}</c:v></c:pt>`
  1323. })
  1324. strXml += ' </c:numCache>'
  1325. strXml += ' </c:numRef>'
  1326. strXml += ' </c:bubbleSize>'
  1327. strXml += ' <c:bubble3D val="' + (chartType === CHART_TYPE.BUBBLE3D ? '1' : '0') + '"/>'
  1328. // F: Close "SERIES"
  1329. strXml += '</c:ser>'
  1330. })
  1331. // 3: Data Labels
  1332. {
  1333. strXml += '<c:dLbls>'
  1334. strXml += `<c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || 'General'}" sourceLinked="0"/>`
  1335. strXml += '<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr>'
  1336. strXml += `<a:defRPr b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0}" strike="noStrike" sz="${Math.round(
  1337. Math.round(opts.dataLabelFontSize || DEF_FONT_SIZE) * 100
  1338. )}" u="none">`
  1339. strXml += `<a:solidFill>${createColorElement(opts.dataLabelColor || DEF_FONT_COLOR)}</a:solidFill>`
  1340. strXml += `<a:latin typeface="${opts.dataLabelFontFace || 'Arial'}"/>`
  1341. strXml += '</a:defRPr></a:pPr></a:p></c:txPr>'
  1342. if (opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`
  1343. strXml += '<c:showLegendKey val="0"/>'
  1344. strXml += `<c:showVal val="${opts.showValue ? '1' : '0'}"/>`
  1345. strXml += `<c:showCatName val="0"/><c:showSerName val="${opts.showSerName ? '1' : '0'}"/><c:showPercent val="0"/><c:showBubbleSize val="0"/>`
  1346. strXml += '<c:extLst>'
  1347. strXml += ' <c:ext uri="{CE6537A1-D6FC-4f65-9D91-7224C49458BB}" xmlns:c15="http://schemas.microsoft.com/office/drawing/2012/chart">'
  1348. strXml += ' <c15:showLeaderLines val="' + (opts.showLeaderLines ? '1' : '0') + '"/>'
  1349. strXml += ' </c:ext>'
  1350. strXml += '</c:extLst>'
  1351. strXml += '</c:dLbls>'
  1352. }
  1353. // 4: Bubble options
  1354. // strXml += ' <c:bubbleScale val="100"/>';
  1355. // strXml += ' <c:showNegBubbles val="0"/>';
  1356. // Commented out to let it default to PPT until we create options
  1357. // 5: AxisId (NOTE: order matters! (category comes first))
  1358. strXml += `<c:axId val="${catAxisId}"/><c:axId val="${valAxisId}"/>`
  1359. // 6: Close Chart tag
  1360. strXml += '</c:bubbleChart>'
  1361. // end switch
  1362. break
  1363. case CHART_TYPE.DOUGHNUT:
  1364. case CHART_TYPE.PIE:
  1365. // Use the same let name so code blocks from barChart are interchangeable
  1366. optsChartData = data[0]
  1367. /* EX:
  1368. data: [
  1369. {
  1370. name: 'Project Status',
  1371. labels: ['Red', 'Amber', 'Green', 'Unknown'],
  1372. values: [10, 20, 38, 2]
  1373. }
  1374. ]
  1375. */
  1376. // 1: Start Chart
  1377. strXml += '<c:' + chartType + 'Chart>'
  1378. strXml += ' <c:varyColors val="1"/>'
  1379. strXml += '<c:ser>'
  1380. strXml += ' <c:idx val="0"/>'
  1381. strXml += ' <c:order val="0"/>'
  1382. strXml += ' <c:tx>'
  1383. strXml += ' <c:strRef>'
  1384. strXml += ' <c:f>Sheet1!$B$1</c:f>'
  1385. strXml += ' <c:strCache>'
  1386. strXml += ' <c:ptCount val="1"/>'
  1387. strXml += ' <c:pt idx="0"><c:v>' + encodeXmlEntities(optsChartData.name) + '</c:v></c:pt>'
  1388. strXml += ' </c:strCache>'
  1389. strXml += ' </c:strRef>'
  1390. strXml += ' </c:tx>'
  1391. strXml += ' <c:spPr>'
  1392. strXml += ' <a:solidFill><a:schemeClr val="accent1"/></a:solidFill>'
  1393. strXml += ' <a:ln w="9525" cap="flat"><a:solidFill><a:srgbClr val="F9F9F9"/></a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>'
  1394. if (opts.dataNoEffects) {
  1395. strXml += '<a:effectLst/>'
  1396. } else {
  1397. strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW)
  1398. }
  1399. strXml += ' </c:spPr>'
  1400. // strXml += '<c:explosion val="0"/>'
  1401. // 2: "Data Point" block for every data row
  1402. optsChartData.labels[0].forEach((_label, idx) => {
  1403. strXml += '<c:dPt>'
  1404. strXml += ` <c:idx val="${idx}"/>`
  1405. strXml += ' <c:bubble3D val="0"/>'
  1406. strXml += ' <c:spPr>'
  1407. strXml += `<a:solidFill>${createColorElement(
  1408. opts.chartColors[idx + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : idx]
  1409. )}</a:solidFill>`
  1410. if (opts.dataBorder) {
  1411. strXml += `<a:ln w="${valToPts(opts.dataBorder.pt)}" cap="flat"><a:solidFill>${createColorElement(
  1412. opts.dataBorder.color
  1413. )}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`
  1414. }
  1415. strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW)
  1416. strXml += ' </c:spPr>'
  1417. strXml += '</c:dPt>'
  1418. })
  1419. // 3: "Data Label" block for every data Label
  1420. strXml += '<c:dLbls>'
  1421. optsChartData.labels[0].forEach((_label, idx) => {
  1422. strXml += '<c:dLbl>'
  1423. strXml += ` <c:idx val="${idx}"/>`
  1424. strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || 'General'}" sourceLinked="0"/>`
  1425. strXml += ' <c:spPr/><c:txPr>'
  1426. strXml += ' <a:bodyPr/><a:lstStyle/>'
  1427. strXml += ' <a:p><a:pPr>'
  1428. strXml += ` <a:defRPr sz="${Math.round((opts.dataLabelFontSize || DEF_FONT_SIZE) * 100)}" b="${opts.dataLabelFontBold ? 1 : 0}" i="${opts.dataLabelFontItalic ? 1 : 0
  1429. }" u="none" strike="noStrike">`
  1430. strXml += ' <a:solidFill>' + createColorElement(opts.dataLabelColor || DEF_FONT_COLOR) + '</a:solidFill>'
  1431. strXml += ` <a:latin typeface="${opts.dataLabelFontFace || 'Arial'}"/>`
  1432. strXml += ' </a:defRPr>'
  1433. strXml += ' </a:pPr></a:p>'
  1434. strXml += ' </c:txPr>'
  1435. if (chartType === CHART_TYPE.PIE && opts.dataLabelPosition) strXml += `<c:dLblPos val="${opts.dataLabelPosition}"/>`
  1436. strXml += ' <c:showLegendKey val="0"/>'
  1437. strXml += ' <c:showVal val="' + (opts.showValue ? '1' : '0') + '"/>'
  1438. strXml += ' <c:showCatName val="' + (opts.showLabel ? '1' : '0') + '"/>'
  1439. strXml += ' <c:showSerName val="' + (opts.showSerName ? '1' : '0') + '"/>'
  1440. strXml += ' <c:showPercent val="' + (opts.showPercent ? '1' : '0') + '"/>'
  1441. strXml += ' <c:showBubbleSize val="0"/>'
  1442. strXml += ' </c:dLbl>'
  1443. })
  1444. strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.dataLabelFormatCode) || 'General'}" sourceLinked="0"/>`
  1445. strXml += ' <c:txPr>'
  1446. strXml += ' <a:bodyPr/>'
  1447. strXml += ' <a:lstStyle/>'
  1448. strXml += ' <a:p>'
  1449. strXml += ' <a:pPr>'
  1450. strXml += ` <a:defRPr sz="1800" b="${opts.dataLabelFontBold ? '1' : '0'}" i="${opts.dataLabelFontItalic ? '1' : '0'}" u="none" strike="noStrike">`
  1451. strXml += ' <a:solidFill><a:srgbClr val="000000"/></a:solidFill><a:latin typeface="Arial"/>'
  1452. strXml += ' </a:defRPr>'
  1453. strXml += ' </a:pPr>'
  1454. strXml += ' </a:p>'
  1455. strXml += ' </c:txPr>'
  1456. strXml += chartType === CHART_TYPE.PIE ? '<c:dLblPos val="ctr"/>' : ''
  1457. strXml += ' <c:showLegendKey val="0"/>'
  1458. strXml += ' <c:showVal val="0"/>'
  1459. strXml += ' <c:showCatName val="1"/>'
  1460. strXml += ' <c:showSerName val="0"/>'
  1461. strXml += ' <c:showPercent val="1"/>'
  1462. strXml += ' <c:showBubbleSize val="0"/>'
  1463. strXml += ` <c:showLeaderLines val="${opts.showLeaderLines ? '1' : '0'}"/>`
  1464. strXml += '</c:dLbls>'
  1465. // 2: "Categories"
  1466. strXml += '<c:cat>'
  1467. strXml += ' <c:strRef>'
  1468. strXml += ` <c:f>Sheet1!$A$2:$A$${optsChartData.labels[0].length + 1}</c:f>`
  1469. strXml += ' <c:strCache>'
  1470. strXml += ` <c:ptCount val="${optsChartData.labels[0].length}"/>`
  1471. optsChartData.labels[0].forEach((label, idx) => {
  1472. strXml += `<c:pt idx="${idx}"><c:v>${encodeXmlEntities(label)}</c:v></c:pt>`
  1473. })
  1474. strXml += ' </c:strCache>'
  1475. strXml += ' </c:strRef>'
  1476. strXml += '</c:cat>'
  1477. // 3: Create vals
  1478. strXml += ' <c:val>'
  1479. strXml += ' <c:numRef>'
  1480. strXml += ` <c:f>Sheet1!$B$2:$B$${optsChartData.labels[0].length + 1}</c:f>`
  1481. strXml += ' <c:numCache>'
  1482. strXml += ` <c:ptCount val="${optsChartData.labels[0].length}"/>`
  1483. optsChartData.values.forEach((value, idx) => {
  1484. strXml += `<c:pt idx="${idx}"><c:v>${value || value === 0 ? value : ''}</c:v></c:pt>`
  1485. })
  1486. strXml += ' </c:numCache>'
  1487. strXml += ' </c:numRef>'
  1488. strXml += ' </c:val>'
  1489. // 4: Close "SERIES"
  1490. strXml += ' </c:ser>'
  1491. strXml += ` <c:firstSliceAng val="${opts.firstSliceAng ? Math.round(opts.firstSliceAng) : 0}"/>`
  1492. if (chartType === CHART_TYPE.DOUGHNUT) strXml += `<c:holeSize val="${typeof opts.holeSize === 'number' ? opts.holeSize : '50'}"/>`
  1493. strXml += '</c:' + chartType + 'Chart>'
  1494. // Done with Doughnut/Pie
  1495. break
  1496. default:
  1497. strXml += ''
  1498. break
  1499. }
  1500. return strXml
  1501. }
  1502. /**
  1503. * Create Category axis
  1504. * @param {IChartOptsLib} opts - chart options
  1505. * @param {string} axisId - value
  1506. * @param {string} valAxisId - value
  1507. * @return {string} XML
  1508. */
  1509. function makeCatAxis (opts: IChartOptsLib, axisId: string, valAxisId: string): string {
  1510. let strXml = ''
  1511. // Build cat axis tag
  1512. // NOTE: Scatter and Bubble chart need two Val axises as they display numbers on x axis
  1513. if (opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D) {
  1514. strXml += '<c:valAx>'
  1515. } else {
  1516. strXml += '<c:' + (opts.catLabelFormatCode ? 'dateAx' : 'catAx') + '>'
  1517. }
  1518. strXml += ' <c:axId val="' + axisId + '"/>'
  1519. strXml += ' <c:scaling>'
  1520. strXml += '<c:orientation val="' + (opts.catAxisOrientation || (opts.barDir === 'col' ? 'minMax' : 'minMax')) + '"/>'
  1521. if (opts.catAxisMaxVal || opts.catAxisMaxVal === 0) strXml += `<c:max val="${opts.catAxisMaxVal}"/>`
  1522. if (opts.catAxisMinVal || opts.catAxisMinVal === 0) strXml += `<c:min val="${opts.catAxisMinVal}"/>`
  1523. strXml += '</c:scaling>'
  1524. strXml += ' <c:delete val="' + (opts.catAxisHidden ? '1' : '0') + '"/>'
  1525. strXml += ' <c:axPos val="' + (opts.barDir === 'col' ? 'b' : 'l') + '"/>'
  1526. strXml += opts.catGridLine.style !== 'none' ? createGridLineElement(opts.catGridLine) : ''
  1527. // '<c:title>' comes between '</c:majorGridlines>' and '<c:numFmt>'
  1528. if (opts.showCatAxisTitle) {
  1529. strXml += genXmlTitle({
  1530. color: opts.catAxisTitleColor,
  1531. fontFace: opts.catAxisTitleFontFace,
  1532. fontSize: opts.catAxisTitleFontSize,
  1533. titleRotate: opts.catAxisTitleRotate,
  1534. title: opts.catAxisTitle || 'Axis Title',
  1535. })
  1536. }
  1537. // NOTE: Adding Val Axis Formatting if scatter or bubble charts
  1538. if (opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D) {
  1539. strXml += ' <c:numFmt formatCode="' + (opts.valAxisLabelFormatCode ? encodeXmlEntities(opts.valAxisLabelFormatCode) : 'General') + '" sourceLinked="1"/>'
  1540. } else {
  1541. strXml += ' <c:numFmt formatCode="' + (encodeXmlEntities(opts.catLabelFormatCode) || 'General') + '" sourceLinked="1"/>'
  1542. }
  1543. if (opts._type === CHART_TYPE.SCATTER) {
  1544. strXml += ' <c:majorTickMark val="none"/>'
  1545. strXml += ' <c:minorTickMark val="none"/>'
  1546. strXml += ' <c:tickLblPos val="nextTo"/>'
  1547. } else {
  1548. strXml += ' <c:majorTickMark val="' + (opts.catAxisMajorTickMark || 'out') + '"/>'
  1549. strXml += ' <c:minorTickMark val="' + (opts.catAxisMinorTickMark || 'none') + '"/>'
  1550. strXml += ' <c:tickLblPos val="' + (opts.catAxisLabelPos || (opts.barDir === 'col' ? 'low' : 'nextTo')) + '"/>'
  1551. }
  1552. strXml += ' <c:spPr>'
  1553. strXml += ` <a:ln w="${opts.catAxisLineSize ? valToPts(opts.catAxisLineSize) : ONEPT}" cap="flat">`
  1554. strXml += !opts.catAxisLineShow ? '<a:noFill/>' : '<a:solidFill>' + createColorElement(opts.catAxisLineColor || DEF_CHART_GRIDLINE.color) + '</a:solidFill>'
  1555. strXml += ' <a:prstDash val="' + (opts.catAxisLineStyle || 'solid') + '"/>'
  1556. strXml += ' <a:round/>'
  1557. strXml += ' </a:ln>'
  1558. strXml += ' </c:spPr>'
  1559. strXml += ' <c:txPr>'
  1560. if (opts.catAxisLabelRotate) {
  1561. strXml += `<a:bodyPr rot="${convertRotationDegrees(opts.catAxisLabelRotate)}"/>`
  1562. } else {
  1563. // NOTE: don't specify "`rot=0" - that way the object will be auto behavior
  1564. strXml += '<a:bodyPr/>'
  1565. }
  1566. strXml += ' <a:lstStyle/>'
  1567. strXml += ' <a:p>'
  1568. strXml += ' <a:pPr>'
  1569. strXml += ` <a:defRPr sz="${Math.round((opts.catAxisLabelFontSize || DEF_FONT_SIZE) * 100)}" b="${opts.catAxisLabelFontBold ? 1 : 0}" i="${opts.catAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`
  1570. strXml += ' <a:solidFill>' + createColorElement(opts.catAxisLabelColor || DEF_FONT_COLOR) + '</a:solidFill>'
  1571. strXml += ' <a:latin typeface="' + (opts.catAxisLabelFontFace || 'Arial') + '"/>'
  1572. strXml += ' </a:defRPr>'
  1573. strXml += ' </a:pPr>'
  1574. strXml += ' <a:endParaRPr lang="' + (opts.lang || 'en-US') + '"/>'
  1575. strXml += ' </a:p>'
  1576. strXml += ' </c:txPr>'
  1577. strXml += ' <c:crossAx val="' + valAxisId + '"/>'
  1578. strXml += ` <c:${typeof opts.valAxisCrossesAt === 'number' ? 'crossesAt' : 'crosses'} val="${opts.valAxisCrossesAt || 'autoZero'}"/>`
  1579. strXml += ' <c:auto val="1"/>'
  1580. strXml += ' <c:lblAlgn val="ctr"/>'
  1581. strXml += ` <c:noMultiLvlLbl val="${opts.catAxisMultiLevelLabels ? 0 : 1}"/>`
  1582. if (opts.catAxisLabelFrequency) strXml += ' <c:tickLblSkip val="' + opts.catAxisLabelFrequency + '"/>'
  1583. // Issue#149: PPT will auto-adjust these as needed after calcing the date bounds, so we only include them when specified by user
  1584. // Allow major and minor units to be set for double value axis charts
  1585. if (opts.catLabelFormatCode || opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D) {
  1586. if (opts.catLabelFormatCode) {
  1587. ['catAxisBaseTimeUnit', 'catAxisMajorTimeUnit', 'catAxisMinorTimeUnit'].forEach(opt => {
  1588. // Validate input as poorly chosen/garbage options will cause chart corruption and it wont render at all!
  1589. if (opts[opt] && (typeof opts[opt] !== 'string' || !['days', 'months', 'years'].includes(opts[opt].toLowerCase()))) {
  1590. console.warn(`"${opt}" must be one of: 'days','months','years' !`)
  1591. opts[opt] = null
  1592. }
  1593. })
  1594. if (opts.catAxisBaseTimeUnit) strXml += '<c:baseTimeUnit val="' + opts.catAxisBaseTimeUnit.toLowerCase() + '"/>'
  1595. if (opts.catAxisMajorTimeUnit) strXml += '<c:majorTimeUnit val="' + opts.catAxisMajorTimeUnit.toLowerCase() + '"/>'
  1596. if (opts.catAxisMinorTimeUnit) strXml += '<c:minorTimeUnit val="' + opts.catAxisMinorTimeUnit.toLowerCase() + '"/>'
  1597. }
  1598. if (opts.catAxisMajorUnit) strXml += `<c:majorUnit val="${opts.catAxisMajorUnit}"/>`
  1599. if (opts.catAxisMinorUnit) strXml += `<c:minorUnit val="${opts.catAxisMinorUnit}"/>`
  1600. }
  1601. // Close cat axis tag
  1602. // NOTE: Added closing tag of val or cat axis based on chart type
  1603. if (opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D) {
  1604. strXml += '</c:valAx>'
  1605. } else {
  1606. strXml += '</c:' + (opts.catLabelFormatCode ? 'dateAx' : 'catAx') + '>'
  1607. }
  1608. return strXml
  1609. }
  1610. /**
  1611. * Create Value Axis (Used by `bar3D`)
  1612. * @param {IChartOptsLib} opts - chart options
  1613. * @param {string} valAxisId - value
  1614. * @return {string} XML
  1615. */
  1616. function makeValAxis (opts: IChartOptsLib, valAxisId: string): string {
  1617. let axisPos = valAxisId === AXIS_ID_VALUE_PRIMARY ? (opts.barDir === 'col' ? 'l' : 'b') : opts.barDir !== 'col' ? 'r' : 't'
  1618. if (valAxisId === AXIS_ID_VALUE_SECONDARY) axisPos = 'r' // default behavior for PPT is showing 2nd val axis on right (primary axis on left)
  1619. const crossAxId = valAxisId === AXIS_ID_VALUE_PRIMARY ? AXIS_ID_CATEGORY_PRIMARY : AXIS_ID_CATEGORY_SECONDARY
  1620. let strXml = ''
  1621. strXml += '<c:valAx>'
  1622. strXml += ' <c:axId val="' + valAxisId + '"/>'
  1623. strXml += ' <c:scaling>'
  1624. if (opts.valAxisLogScaleBase) strXml += `<c:logBase val="${opts.valAxisLogScaleBase}"/>`
  1625. strXml += '<c:orientation val="' + (opts.valAxisOrientation || (opts.barDir === 'col' ? 'minMax' : 'minMax')) + '"/>'
  1626. if (opts.valAxisMaxVal || opts.valAxisMaxVal === 0) strXml += `<c:max val="${opts.valAxisMaxVal}"/>`
  1627. if (opts.valAxisMinVal || opts.valAxisMinVal === 0) strXml += `<c:min val="${opts.valAxisMinVal}"/>`
  1628. strXml += ' </c:scaling>'
  1629. strXml += ` <c:delete val="${opts.valAxisHidden ? 1 : 0}"/>`
  1630. strXml += ' <c:axPos val="' + axisPos + '"/>'
  1631. if (opts.valGridLine.style !== 'none') strXml += createGridLineElement(opts.valGridLine)
  1632. // '<c:title>' comes between '</c:majorGridlines>' and '<c:numFmt>'
  1633. if (opts.showValAxisTitle) {
  1634. strXml += genXmlTitle({
  1635. color: opts.valAxisTitleColor,
  1636. fontFace: opts.valAxisTitleFontFace,
  1637. fontSize: opts.valAxisTitleFontSize,
  1638. titleRotate: opts.valAxisTitleRotate,
  1639. title: opts.valAxisTitle || 'Axis Title',
  1640. })
  1641. }
  1642. strXml += `<c:numFmt formatCode="${opts.valAxisLabelFormatCode ? encodeXmlEntities(opts.valAxisLabelFormatCode) : 'General'}" sourceLinked="0"/>`
  1643. if (opts._type === CHART_TYPE.SCATTER) {
  1644. strXml += ' <c:majorTickMark val="none"/>'
  1645. strXml += ' <c:minorTickMark val="none"/>'
  1646. strXml += ' <c:tickLblPos val="nextTo"/>'
  1647. } else {
  1648. strXml += ' <c:majorTickMark val="' + (opts.valAxisMajorTickMark || 'out') + '"/>'
  1649. strXml += ' <c:minorTickMark val="' + (opts.valAxisMinorTickMark || 'none') + '"/>'
  1650. strXml += ' <c:tickLblPos val="' + (opts.valAxisLabelPos || (opts.barDir === 'col' ? 'nextTo' : 'low')) + '"/>'
  1651. }
  1652. strXml += ' <c:spPr>'
  1653. strXml += ` <a:ln w="${opts.valAxisLineSize ? valToPts(opts.valAxisLineSize) : ONEPT}" cap="flat">`
  1654. strXml += !opts.valAxisLineShow ? '<a:noFill/>' : '<a:solidFill>' + createColorElement(opts.valAxisLineColor || DEF_CHART_GRIDLINE.color) + '</a:solidFill>'
  1655. strXml += ' <a:prstDash val="' + (opts.valAxisLineStyle || 'solid') + '"/>'
  1656. strXml += ' <a:round/>'
  1657. strXml += ' </a:ln>'
  1658. strXml += ' </c:spPr>'
  1659. strXml += ' <c:txPr>'
  1660. strXml += ` <a:bodyPr${opts.valAxisLabelRotate ? (' rot="' + convertRotationDegrees(opts.valAxisLabelRotate).toString() + '"') : ''}/>` // don't specify rot 0 so we get the auto behavior
  1661. strXml += ' <a:lstStyle/>'
  1662. strXml += ' <a:p>'
  1663. strXml += ' <a:pPr>'
  1664. strXml += ` <a:defRPr sz="${Math.round((opts.valAxisLabelFontSize || DEF_FONT_SIZE) * 100)}" b="${opts.valAxisLabelFontBold ? 1 : 0}" i="${opts.valAxisLabelFontItalic ? 1 : 0}" u="none" strike="noStrike">`
  1665. strXml += ' <a:solidFill>' + createColorElement(opts.valAxisLabelColor || DEF_FONT_COLOR) + '</a:solidFill>'
  1666. strXml += ' <a:latin typeface="' + (opts.valAxisLabelFontFace || 'Arial') + '"/>'
  1667. strXml += ' </a:defRPr>'
  1668. strXml += ' </a:pPr>'
  1669. strXml += ' <a:endParaRPr lang="' + (opts.lang || 'en-US') + '"/>'
  1670. strXml += ' </a:p>'
  1671. strXml += ' </c:txPr>'
  1672. strXml += ' <c:crossAx val="' + crossAxId + '"/>'
  1673. if (typeof opts.catAxisCrossesAt === 'number') {
  1674. strXml += ` <c:crossesAt val="${opts.catAxisCrossesAt}"/>`
  1675. } else if (typeof opts.catAxisCrossesAt === 'string') {
  1676. strXml += ' <c:crosses val="' + opts.catAxisCrossesAt + '"/>'
  1677. } else {
  1678. const isRight = axisPos === 'r' || axisPos === 't'
  1679. const crosses = isRight ? 'max' : 'autoZero'
  1680. strXml += ' <c:crosses val="' + crosses + '"/>'
  1681. }
  1682. strXml +=
  1683. ' <c:crossBetween val="' +
  1684. (opts._type === CHART_TYPE.SCATTER || (!!(Array.isArray(opts._type) && opts._type.filter(type => type.type === CHART_TYPE.AREA).length > 0)) ? 'midCat' : 'between') +
  1685. '"/>'
  1686. if (opts.valAxisMajorUnit) strXml += ` <c:majorUnit val="${opts.valAxisMajorUnit}"/>`
  1687. if (opts.valAxisDisplayUnit) { strXml += `<c:dispUnits><c:builtInUnit val="${opts.valAxisDisplayUnit}"/>${opts.valAxisDisplayUnitLabel ? '<c:dispUnitsLbl/>' : ''}</c:dispUnits>` }
  1688. strXml += '</c:valAx>'
  1689. return strXml
  1690. }
  1691. /**
  1692. * Create Series Axis (Used by `bar3D`)
  1693. * @param {IChartOptsLib} opts - chart options
  1694. * @param {string} axisId - axis ID
  1695. * @param {string} valAxisId - value
  1696. * @return {string} XML
  1697. */
  1698. function makeSerAxis (opts: IChartOptsLib, axisId: string, valAxisId: string): string {
  1699. let strXml = ''
  1700. // Build ser axis tag
  1701. strXml += '<c:serAx>'
  1702. strXml += ' <c:axId val="' + axisId + '"/>'
  1703. strXml += ' <c:scaling><c:orientation val="' + (opts.serAxisOrientation || (opts.barDir === 'col' ? 'minMax' : 'minMax')) + '"/></c:scaling>'
  1704. strXml += ' <c:delete val="' + (opts.serAxisHidden ? '1' : '0') + '"/>'
  1705. strXml += ' <c:axPos val="' + (opts.barDir === 'col' ? 'b' : 'l') + '"/>'
  1706. strXml += opts.serGridLine.style !== 'none' ? createGridLineElement(opts.serGridLine) : ''
  1707. // '<c:title>' comes between '</c:majorGridlines>' and '<c:numFmt>'
  1708. if (opts.showSerAxisTitle) {
  1709. strXml += genXmlTitle({
  1710. color: opts.serAxisTitleColor,
  1711. fontFace: opts.serAxisTitleFontFace,
  1712. fontSize: opts.serAxisTitleFontSize,
  1713. titleRotate: opts.serAxisTitleRotate,
  1714. title: opts.serAxisTitle || 'Axis Title',
  1715. })
  1716. }
  1717. strXml += ` <c:numFmt formatCode="${encodeXmlEntities(opts.serLabelFormatCode) || 'General'}" sourceLinked="0"/>`
  1718. strXml += ' <c:majorTickMark val="out"/>'
  1719. strXml += ' <c:minorTickMark val="none"/>'
  1720. strXml += ` <c:tickLblPos val="${opts.serAxisLabelPos || opts.barDir === 'col' ? 'low' : 'nextTo'}"/>`
  1721. strXml += ' <c:spPr>'
  1722. strXml += ' <a:ln w="12700" cap="flat">'
  1723. strXml += !opts.serAxisLineShow ? '<a:noFill/>' : `<a:solidFill>${createColorElement(opts.serAxisLineColor || DEF_CHART_GRIDLINE.color)}</a:solidFill>`
  1724. strXml += ' <a:prstDash val="solid"/>'
  1725. strXml += ' <a:round/>'
  1726. strXml += ' </a:ln>'
  1727. strXml += ' </c:spPr>'
  1728. strXml += ' <c:txPr>'
  1729. strXml += ' <a:bodyPr/>' // don't specify rot 0 so we get the auto behavior
  1730. strXml += ' <a:lstStyle/>'
  1731. strXml += ' <a:p>'
  1732. strXml += ' <a:pPr>'
  1733. strXml += ` <a:defRPr sz="${Math.round((opts.serAxisLabelFontSize || DEF_FONT_SIZE) * 100)}" b="${opts.serAxisLabelFontBold ? '1' : '0'}" i="${opts.serAxisLabelFontItalic ? '1' : '0'}" u="none" strike="noStrike">`
  1734. strXml += ` <a:solidFill>${createColorElement(opts.serAxisLabelColor || DEF_FONT_COLOR)}</a:solidFill>`
  1735. strXml += ` <a:latin typeface="${opts.serAxisLabelFontFace || 'Arial'}"/>`
  1736. strXml += ' </a:defRPr>'
  1737. strXml += ' </a:pPr>'
  1738. strXml += ' <a:endParaRPr lang="' + (opts.lang || 'en-US') + '"/>'
  1739. strXml += ' </a:p>'
  1740. strXml += ' </c:txPr>'
  1741. strXml += ' <c:crossAx val="' + valAxisId + '"/>'
  1742. strXml += ' <c:crosses val="autoZero"/>'
  1743. if (opts.serAxisLabelFrequency) strXml += ' <c:tickLblSkip val="' + opts.serAxisLabelFrequency + '"/>'
  1744. // Issue#149: PPT will auto-adjust these as needed after calcing the date bounds, so we only include them when specified by user
  1745. if (opts.serLabelFormatCode) {
  1746. ['serAxisBaseTimeUnit', 'serAxisMajorTimeUnit', 'serAxisMinorTimeUnit'].forEach(opt => {
  1747. // Validate input as poorly chosen/garbage options will cause chart corruption and it wont render at all!
  1748. if (opts[opt] && (typeof opts[opt] !== 'string' || !['days', 'months', 'years'].includes(opt.toLowerCase()))) {
  1749. console.warn(`"${opt}" must be one of: 'days','months','years' !`)
  1750. opts[opt] = null
  1751. }
  1752. })
  1753. if (opts.serAxisBaseTimeUnit) strXml += ` <c:baseTimeUnit val="${opts.serAxisBaseTimeUnit.toLowerCase()}"/>`
  1754. if (opts.serAxisMajorTimeUnit) strXml += ` <c:majorTimeUnit val="${opts.serAxisMajorTimeUnit.toLowerCase()}"/>`
  1755. if (opts.serAxisMinorTimeUnit) strXml += ` <c:minorTimeUnit val="${opts.serAxisMinorTimeUnit.toLowerCase()}"/>`
  1756. if (opts.serAxisMajorUnit) strXml += ` <c:majorUnit val="${opts.serAxisMajorUnit}"/>`
  1757. if (opts.serAxisMinorUnit) strXml += ` <c:minorUnit val="${opts.serAxisMinorUnit}"/>`
  1758. }
  1759. // Close ser axis tag
  1760. strXml += '</c:serAx>'
  1761. return strXml
  1762. }
  1763. /**
  1764. * Create char title elements
  1765. * @param {IChartPropsTitle} opts - options
  1766. * @return {string} XML `<c:title>`
  1767. */
  1768. function genXmlTitle (opts: IChartPropsTitle, chartX?: number, chartY?: number): string {
  1769. const align = opts.titleAlign === 'left' || opts.titleAlign === 'right' ? `<a:pPr algn="${opts.titleAlign.substring(0, 1)}">` : '<a:pPr>'
  1770. const rotate = opts.titleRotate ? `<a:bodyPr rot="${convertRotationDegrees(opts.titleRotate)}"/>` : '<a:bodyPr/>' // don't specify rotation to get default (ex. vertical for cat axis)
  1771. const sizeAttr = opts.fontSize ? `sz="${Math.round(opts.fontSize * 100)}"` : '' // only set the font size if specified. Powerpoint will handle the default size
  1772. const titleBold = opts.titleBold ? 1 : 0
  1773. let layout = '<c:layout/>'
  1774. if (opts.titlePos && typeof opts.titlePos.x === 'number' && typeof opts.titlePos.y === 'number') {
  1775. // NOTE: manualLayout x/y vals are *relative to entire slide*
  1776. const totalX = opts.titlePos.x + chartX
  1777. const totalY = opts.titlePos.y + chartY
  1778. let valX = totalX === 0 ? 0 : (totalX * (totalX / 5)) / 10
  1779. if (valX >= 1) valX = valX / 10
  1780. if (valX >= 0.1) valX = valX / 10
  1781. let valY = totalY === 0 ? 0 : (totalY * (totalY / 5)) / 10
  1782. if (valY >= 1) valY = valY / 10
  1783. if (valY >= 0.1) valY = valY / 10
  1784. layout = `<c:layout><c:manualLayout><c:xMode val="edge"/><c:yMode val="edge"/><c:x val="${valX}"/><c:y val="${valY}"/></c:manualLayout></c:layout>`
  1785. }
  1786. return `<c:title>
  1787. <c:tx>
  1788. <c:rich>
  1789. ${rotate}
  1790. <a:lstStyle/>
  1791. <a:p>
  1792. ${align}
  1793. <a:defRPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
  1794. <a:solidFill>${createColorElement(opts.color || DEF_FONT_COLOR)}</a:solidFill>
  1795. <a:latin typeface="${opts.fontFace || 'Arial'}"/>
  1796. </a:defRPr>
  1797. </a:pPr>
  1798. <a:r>
  1799. <a:rPr ${sizeAttr} b="${titleBold}" i="0" u="none" strike="noStrike">
  1800. <a:solidFill>${createColorElement(opts.color || DEF_FONT_COLOR)}</a:solidFill>
  1801. <a:latin typeface="${opts.fontFace || 'Arial'}"/>
  1802. </a:rPr>
  1803. <a:t>${encodeXmlEntities(opts.title) || ''}</a:t>
  1804. </a:r>
  1805. </a:p>
  1806. </c:rich>
  1807. </c:tx>
  1808. ${layout}
  1809. <c:overlay val="0"/>
  1810. </c:title>`
  1811. }
  1812. /**
  1813. * Calc and return excel column name for a given column length
  1814. * @param colIndex column index
  1815. * @return column name
  1816. * @example 1 returns 'A'
  1817. * @example 27 returns 'AA'
  1818. */
  1819. function getExcelColName (colIndex: number): string {
  1820. let colStr = ''
  1821. const colIdx = colIndex - 1 // Subtract 1 so `LETTERS[columnIndex]` returns "A" etc
  1822. if (colIdx <= 25) {
  1823. // A-Z
  1824. colStr = LETTERS[colIdx]
  1825. } else {
  1826. // AA-ZZ (ZZ = index 702)
  1827. colStr = `${LETTERS[Math.floor(colIdx / LETTERS.length - 1)]}${LETTERS[colIdx % LETTERS.length]}`
  1828. }
  1829. return colStr
  1830. }
  1831. /**
  1832. * Creates `a:innerShdw` or `a:outerShdw` depending on pass options `opts`.
  1833. * @param {Object} opts optional shadow properties
  1834. * @param {Object} defaults defaults for unspecified properties in `opts`
  1835. * @see http://officeopenxml.com/drwSp-effects.php
  1836. * @example { type: 'outer', blur: 3, offset: (23000 / 12700), angle: 90, color: '000000', opacity: 0.35, rotateWithShape: true };
  1837. * @return {string} XML
  1838. */
  1839. function createShadowElement (options: ShadowProps, defaults: object): string {
  1840. if (!options) {
  1841. return '<a:effectLst/>'
  1842. } else if (typeof options !== 'object') {
  1843. console.warn('`shadow` options must be an object. Ex: `{shadow: {type:\'none\'}}`')
  1844. return '<a:effectLst/>'
  1845. }
  1846. let strXml = '<a:effectLst>'
  1847. const opts = { ...defaults, ...options }
  1848. const type = opts.type || 'outer'
  1849. const blur = valToPts(opts.blur)
  1850. const offset = valToPts(opts.offset)
  1851. const angle = Math.round(opts.angle * 60000)
  1852. const color = opts.color
  1853. const opacity = Math.round(opts.opacity * 100000)
  1854. const rotShape = opts.rotateWithShape ? 1 : 0
  1855. strXml += `<a:${type}Shdw sx="100000" sy="100000" kx="0" ky="0" algn="bl" blurRad="${blur}" rotWithShape="${rotShape}" dist="${offset}" dir="${angle}">`
  1856. strXml += `<a:srgbClr val="${color}">`
  1857. strXml += `<a:alpha val="${opacity}"/></a:srgbClr>`
  1858. strXml += `</a:${type}Shdw>`
  1859. strXml += '</a:effectLst>'
  1860. return strXml
  1861. }
  1862. /**
  1863. * Create Grid Line Element
  1864. * @param {OptsChartGridLine} glOpts {size, color, style}
  1865. * @return {string} XML
  1866. */
  1867. function createGridLineElement (glOpts: OptsChartGridLine): string {
  1868. let strXml = '<c:majorGridlines>'
  1869. strXml += ' <c:spPr>'
  1870. strXml += ` <a:ln w="${valToPts(glOpts.size || DEF_CHART_GRIDLINE.size)}" cap="${createLineCap(glOpts.cap || DEF_CHART_GRIDLINE.cap)}">`
  1871. strXml += ' <a:solidFill><a:srgbClr val="' + (glOpts.color || DEF_CHART_GRIDLINE.color) + '"/></a:solidFill>' // should accept scheme colors as implemented in [Pull #135]
  1872. strXml += ' <a:prstDash val="' + (glOpts.style || DEF_CHART_GRIDLINE.style) + '"/><a:round/>'
  1873. strXml += ' </a:ln>'
  1874. strXml += ' </c:spPr>'
  1875. strXml += '</c:majorGridlines>'
  1876. return strXml
  1877. }
  1878. function createLineCap (lineCap: ChartLineCap): string {
  1879. if (!lineCap || lineCap === 'flat') {
  1880. return 'flat'
  1881. } else if (lineCap === 'square') {
  1882. return 'sq'
  1883. } else if (lineCap === 'round') {
  1884. return 'rnd'
  1885. } else {
  1886. const neverLineCap: never = lineCap
  1887. throw new Error(`Invalid chart line cap: ${neverLineCap}`)
  1888. }
  1889. }