0

I am reading an XLSX file as a template and fill out a couple of existing worksheets. Some other worksheet in the file that I don't touch contains links to the updated worksheets and also charts based on these linked cells.

When I save the template as a new XLSX file after filling in the data the charts are gone from the other worksheet that I don't even touch.

I added an "includeCharts" statement to the writer but that doesn't help either

$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$writer->setIncludeCharts(true);
$writer->save($systemPath);

Any idea what I need to do in order to avoid losing the charts?

rf1234
  • 1,510
  • 12
  • 13

2 Answers2

0

Found the answer here. The last comment from nic86 (4 days ago) did the trick. But I only got it working for xlsx which is good enough for me.

Excel from template didn't copy charts. #382

Subsequently I got stuck with another issue: If you want to have charts based on dynamic named ranges: bad luck. There is a bug that has been noted a while ago but nothing seems to happen. Also here: No response to my question ... This is the bug description: Defined Name Range from template file not copying to file #462

To be more precise: This works (static range)

 $spreadsheet->addNamedRange( new NamedRange('DRange_AvgVarInt_modified', $worksheet, 'A1', true) );

This doesn't work (dynamic range)

$spreadsheet->addNamedRange( new NamedRange('DRange_AvgVarRate', $worksheet, '=OFFSET(midlayer!$B$7,0,0,1,MIN(BaseData!$C$2,36))', true) );

The error message is:

Exception: Invalid cell coordinate 36))

Apparently PHPSpreadsheet tries to resolve the expression instead of simply copying it. Any idea about a work around? Or could anyone provide a bug fix. I am not a good enough coder to do it myself unfortunately. But I would really hate to have to go back to static ranges ...

rf1234
  • 1,510
  • 12
  • 13
0

The work around is actually to dynamically create static ranges programmatically. No other way unfortunately. In my case my charts are fully dynamic, i.e. the base table's length is dynamic, too. I tried to get that resolved with PHP Spreadsheet by manipulating the chart dynamically like this. It basically worked but part of my changes were overwritten afterwards so it wasn't really successful.Now I decided to give up: I will create all of my charts programmatically now because changing a chart from a template has too many limitations if you are using dynamic charts.

foreach ($worksheet->getChartCollection() as $chart) {
if ($chart instanceof Chart) {
    $plotArea = $chart->getPlotArea(); //get the plot area of the chart (one thing)
    $dataSeries = $plotArea->getPlotGroup(); //array of all the data series
    $dataManipulated = false;
    foreach ($dataSeries as &$dataSer) { //by reference to change the values deep down!!
        $val = $dataSer->getPlotValues();
        foreach ( $val as &$dataSeriesValues) {
            $dataSource = $dataSeriesValues->getDataSource();
            $dataValues = $dataSeriesValues->getDataValues();
            $dataValuesLength = count($dataValues);
            for ($y=$pointCount; $y < $dataValuesLength; $y++) {
                unset($dataValues[$y]);
            }                        
            if ( strpos($dataSource, 'DRange_Dates_' . $tf ) !== false ) {
                $dataSeriesValues->setDataSource( $sheetName . '!D2:' . $xC . '2' );                            
            } elseif ( strpos($dataSource, 'DRange_AvgVarInt_modified_' . $tf ) !== false ) {
                $dataSeriesValues->setDataSource( 'midlayer!B10:' . $xCML . '10' ); 
            } elseif ( strpos($dataSource, 'DRange_AvgVarRate_' . $tf ) !== false ) {
                $dataSeriesValues->setDataSource( 'midlayer!B7:' . $xCML . '7' ); 
            }
            $dataSeriesValues->setDataValues( $dataValues );
        }
        $cat = $dataSer->getPlotCategories();
        foreach ( $cat as &$categoryValues) {
            $dataSource = $categoryValues->getDataSource();
            $dataValues = $categoryValues->getDataValues();
            $dataValuesLength = count($dataValues);
            for ($y=$pointCount; $y < $dataValuesLength; $y++) {
                unset($dataValues[$y]);
            }                        
            if ( strpos($dataSource, 'DRange_Dates_' . $tf ) !== false ) {
                $categoryValues->setDataSource( $sheetName . '!D2:' . $xC . '2' );                            
            } elseif ( strpos($dataSource, 'DRange_AvgVarInt_modified_' . $tf ) !== false ) {
                $categoryValues->setDataSource( 'midlayer!B10:' . $xCML . '10' ); 
            } elseif ( strpos($dataSource, 'DRange_AvgVarRate_' . $tf ) !== false ) {
                $categoryValues->setDataSource( 'midlayer!B7:' . $xCML . '7' ); 
            }
            $categoryValues->setDataValues( $dataValues );
        }
    }
    $plotArea->setPlotSeries($dataSeries); 
    unset($dataSeriesValues); // break the reference with the last element
    unset($categoryValues); // break the reference with the last element
    unset($dataSer); // break the reference with the last element               
}

}

If you want you charts to look a little nicer you run into the next obstacle. You cannot set the color scheme of the charts youself.

class Theme extends WriterPart

contains this

private static $colourScheme = [
    'dk2' => '1F497D',
    'lt2' => 'EEECE1',
    'accent1' => '4F81BD',
    'accent2' => 'C0504D',
    'accent3' => '9BBB59',
    'accent4' => '8064A2',
    'accent5' => '4BACC6',
    'accent6' => 'F79646',
    'hlink' => '0000FF',
    'folHlink' => '800080',
];

This is the standard Office color scheme in hex. Using this your charts will look like everbody else's charts. If you don't want that you can't change it except for a hard coded change which of course isn't the best choice. But I didn't find another way. If anyone has a better way please tell me. Thanks. It would also be great to use several color schemes instead of just one. Didn't find a way to do this either unfortunately.

rf1234
  • 1,510
  • 12
  • 13