diff options
Diffstat (limited to 'chart2/source/view/main')
24 files changed, 12384 insertions, 0 deletions
diff --git a/chart2/source/view/main/ChartItemPool.cxx b/chart2/source/view/main/ChartItemPool.cxx new file mode 100644 index 000000000..7e428942f --- /dev/null +++ b/chart2/source/view/main/ChartItemPool.cxx @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ChartItemPool.hxx" +#include <chartview/ChartSfxItemIds.hxx> +#include <svx/chrtitem.hxx> +#include <svl/intitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/sizeitem.hxx> +#include <svl/stritem.hxx> +#include <svl/ilstitem.hxx> +#include <editeng/editids.hrc> +#include <svx/svxids.hrc> +#include <vector> + +#include <com/sun/star/chart2/LegendPosition.hpp> + +namespace chart +{ + +ChartItemPool::ChartItemPool(): + SfxItemPool( "ChartItemPool" , SCHATTR_START, SCHATTR_END, nullptr, nullptr ), + pItemInfos(new SfxItemInfo[SCHATTR_END - SCHATTR_START + 1]) +{ + /************************************************************************** + * PoolDefaults + **************************************************************************/ + std::vector<SfxPoolItem*>* ppPoolDefaults = new std::vector<SfxPoolItem*>(SCHATTR_END - SCHATTR_START + 1); + std::vector<SfxPoolItem*>& rPoolDefaults = *ppPoolDefaults; + rPoolDefaults[SCHATTR_DATADESCR_SHOW_NUMBER - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_NUMBER); + rPoolDefaults[SCHATTR_DATADESCR_SHOW_PERCENTAGE- SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_PERCENTAGE); + rPoolDefaults[SCHATTR_DATADESCR_SHOW_CATEGORY - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_CATEGORY); + rPoolDefaults[SCHATTR_DATADESCR_SHOW_SYMBOL - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_SHOW_SYMBOL); + rPoolDefaults[SCHATTR_DATADESCR_WRAP_TEXT - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_WRAP_TEXT); + rPoolDefaults[SCHATTR_DATADESCR_SEPARATOR - SCHATTR_START] = new SfxStringItem(SCHATTR_DATADESCR_SEPARATOR," "); + rPoolDefaults[SCHATTR_DATADESCR_PLACEMENT - SCHATTR_START] = new SfxInt32Item(SCHATTR_DATADESCR_PLACEMENT,0); + rPoolDefaults[SCHATTR_DATADESCR_AVAILABLE_PLACEMENTS - SCHATTR_START] = new SfxIntegerListItem(SCHATTR_DATADESCR_AVAILABLE_PLACEMENTS, std::vector < sal_Int32 >() ); + rPoolDefaults[SCHATTR_DATADESCR_NO_PERCENTVALUE - SCHATTR_START] = new SfxBoolItem(SCHATTR_DATADESCR_NO_PERCENTVALUE); + rPoolDefaults[SCHATTR_PERCENT_NUMBERFORMAT_VALUE - SCHATTR_START] = new SfxUInt32Item(SCHATTR_PERCENT_NUMBERFORMAT_VALUE, 0); + rPoolDefaults[SCHATTR_PERCENT_NUMBERFORMAT_SOURCE - SCHATTR_START] = new SfxBoolItem(SCHATTR_PERCENT_NUMBERFORMAT_SOURCE); + + //legend + rPoolDefaults[SCHATTR_LEGEND_POS - SCHATTR_START] = new SfxInt32Item(SCHATTR_LEGEND_POS, sal_Int32(css::chart2::LegendPosition_LINE_END) ); + rPoolDefaults[SCHATTR_LEGEND_SHOW - SCHATTR_START] = new SfxBoolItem(SCHATTR_LEGEND_SHOW, true); + rPoolDefaults[SCHATTR_LEGEND_NO_OVERLAY - SCHATTR_START] = new SfxBoolItem(SCHATTR_LEGEND_NO_OVERLAY, true); + + //text + rPoolDefaults[SCHATTR_TEXT_DEGREES - SCHATTR_START] = new SfxInt32Item(SCHATTR_TEXT_DEGREES, 0); + rPoolDefaults[SCHATTR_TEXT_STACKED - SCHATTR_START] = new SfxBoolItem(SCHATTR_TEXT_STACKED,false); + + //statistic + rPoolDefaults[SCHATTR_STAT_AVERAGE - SCHATTR_START] = new SfxBoolItem (SCHATTR_STAT_AVERAGE); + rPoolDefaults[SCHATTR_STAT_KIND_ERROR - SCHATTR_START] = new SvxChartKindErrorItem (SvxChartKindError::NONE, SCHATTR_STAT_KIND_ERROR); + rPoolDefaults[SCHATTR_STAT_PERCENT - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_PERCENT); + rPoolDefaults[SCHATTR_STAT_BIGERROR - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_BIGERROR); + rPoolDefaults[SCHATTR_STAT_CONSTPLUS - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_CONSTPLUS); + rPoolDefaults[SCHATTR_STAT_CONSTMINUS - SCHATTR_START] = new SvxDoubleItem (0.0, SCHATTR_STAT_CONSTMINUS); + rPoolDefaults[SCHATTR_STAT_INDICATE - SCHATTR_START] = new SvxChartIndicateItem (SvxChartIndicate::NONE, SCHATTR_STAT_INDICATE); + rPoolDefaults[SCHATTR_STAT_RANGE_POS - SCHATTR_START] = new SfxStringItem (SCHATTR_STAT_RANGE_POS, OUString()); + rPoolDefaults[SCHATTR_STAT_RANGE_NEG - SCHATTR_START] = new SfxStringItem (SCHATTR_STAT_RANGE_NEG, OUString()); + rPoolDefaults[SCHATTR_STAT_ERRORBAR_TYPE - SCHATTR_START] = new SfxBoolItem(SCHATTR_STAT_ERRORBAR_TYPE, true); + + rPoolDefaults[SCHATTR_STYLE_DEEP - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_DEEP, false); + rPoolDefaults[SCHATTR_STYLE_3D - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_3D, false); + rPoolDefaults[SCHATTR_STYLE_VERTICAL - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_VERTICAL, false); + rPoolDefaults[SCHATTR_STYLE_BASETYPE - SCHATTR_START] = new SfxInt32Item(SCHATTR_STYLE_BASETYPE, 0); + rPoolDefaults[SCHATTR_STYLE_LINES - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_LINES, false); + rPoolDefaults[SCHATTR_STYLE_PERCENT - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_PERCENT, false); + rPoolDefaults[SCHATTR_STYLE_STACKED - SCHATTR_START] = new SfxBoolItem (SCHATTR_STYLE_STACKED, false); + rPoolDefaults[SCHATTR_STYLE_SPLINES - SCHATTR_START] = new SfxInt32Item (SCHATTR_STYLE_SPLINES, 0); //Bug: was Bool! test ->Fileformat (touches only 5's) + rPoolDefaults[SCHATTR_STYLE_SYMBOL - SCHATTR_START] = new SfxInt32Item (SCHATTR_STYLE_SYMBOL, 0); + rPoolDefaults[SCHATTR_STYLE_SHAPE - SCHATTR_START] = new SfxInt32Item (SCHATTR_STYLE_SHAPE, 0); + + rPoolDefaults[SCHATTR_AXIS - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS,2); //2 = Y-Axis!!! + + //axis scale + rPoolDefaults[SCHATTR_AXISTYPE - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXISTYPE, CHART_AXIS_REALNUMBER); + rPoolDefaults[SCHATTR_AXIS_REVERSE - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_REVERSE,false); + rPoolDefaults[SCHATTR_AXIS_AUTO_MIN - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_MIN); + rPoolDefaults[SCHATTR_AXIS_MIN - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_MIN); + rPoolDefaults[SCHATTR_AXIS_AUTO_MAX - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_MAX); + rPoolDefaults[SCHATTR_AXIS_MAX - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_MAX); + rPoolDefaults[SCHATTR_AXIS_AUTO_STEP_MAIN - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_STEP_MAIN); + rPoolDefaults[SCHATTR_AXIS_STEP_MAIN - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_STEP_MAIN); + rPoolDefaults[SCHATTR_AXIS_MAIN_TIME_UNIT - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_MAIN_TIME_UNIT,2); + rPoolDefaults[SCHATTR_AXIS_AUTO_STEP_HELP - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_STEP_HELP); + rPoolDefaults[SCHATTR_AXIS_STEP_HELP - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_STEP_HELP,0); + rPoolDefaults[SCHATTR_AXIS_HELP_TIME_UNIT - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_HELP_TIME_UNIT,2); + rPoolDefaults[SCHATTR_AXIS_AUTO_TIME_RESOLUTION - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_TIME_RESOLUTION); + rPoolDefaults[SCHATTR_AXIS_TIME_RESOLUTION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_TIME_RESOLUTION,2); + rPoolDefaults[SCHATTR_AXIS_LOGARITHM - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_LOGARITHM); + rPoolDefaults[SCHATTR_AXIS_AUTO_DATEAXIS - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_DATEAXIS); + rPoolDefaults[SCHATTR_AXIS_ALLOW_DATEAXIS - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_ALLOW_DATEAXIS); + rPoolDefaults[SCHATTR_AXIS_AUTO_ORIGIN - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_AUTO_ORIGIN); + rPoolDefaults[SCHATTR_AXIS_ORIGIN - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_ORIGIN); + + //axis position + rPoolDefaults[SCHATTR_AXIS_TICKS - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_TICKS,CHAXIS_MARK_OUTER); + rPoolDefaults[SCHATTR_AXIS_HELPTICKS - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_HELPTICKS,0); + rPoolDefaults[SCHATTR_AXIS_POSITION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_POSITION,0); + rPoolDefaults[SCHATTR_AXIS_POSITION_VALUE - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_AXIS_POSITION_VALUE); + rPoolDefaults[SCHATTR_AXIS_CROSSING_MAIN_AXIS_NUMBERFORMAT - SCHATTR_START] = new SfxUInt32Item(SCHATTR_AXIS_CROSSING_MAIN_AXIS_NUMBERFORMAT,0); + rPoolDefaults[SCHATTR_AXIS_SHIFTED_CATEGORY_POSITION - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_SHIFTED_CATEGORY_POSITION,false); + rPoolDefaults[SCHATTR_AXIS_LABEL_POSITION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_LABEL_POSITION,0); + rPoolDefaults[SCHATTR_AXIS_MARK_POSITION - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_MARK_POSITION,0); + + //axis label + rPoolDefaults[SCHATTR_AXIS_SHOWDESCR - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_SHOWDESCR,false); + rPoolDefaults[SCHATTR_AXIS_LABEL_ORDER - SCHATTR_START] = new SvxChartTextOrderItem(SvxChartTextOrder::SideBySide, SCHATTR_AXIS_LABEL_ORDER); + rPoolDefaults[SCHATTR_AXIS_LABEL_OVERLAP - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_LABEL_OVERLAP,false); + rPoolDefaults[SCHATTR_AXIS_LABEL_BREAK - SCHATTR_START] = new SfxBoolItem(SCHATTR_AXIS_LABEL_BREAK, false ); + + rPoolDefaults[SCHATTR_SYMBOL_BRUSH - SCHATTR_START] = new SvxBrushItem(SCHATTR_SYMBOL_BRUSH); + rPoolDefaults[SCHATTR_STOCK_VOLUME - SCHATTR_START] = new SfxBoolItem(SCHATTR_STOCK_VOLUME,false); + rPoolDefaults[SCHATTR_STOCK_UPDOWN - SCHATTR_START] = new SfxBoolItem(SCHATTR_STOCK_UPDOWN,false); + rPoolDefaults[SCHATTR_SYMBOL_SIZE - SCHATTR_START] = new SvxSizeItem(SCHATTR_SYMBOL_SIZE,Size(0,0)); + rPoolDefaults[SCHATTR_HIDE_DATA_POINT_LEGEND_ENTRY - SCHATTR_START] = new SfxBoolItem(SCHATTR_HIDE_DATA_POINT_LEGEND_ENTRY, false); + + // new for New Chart + rPoolDefaults[SCHATTR_BAR_OVERLAP - SCHATTR_START] = new SfxInt32Item(SCHATTR_BAR_OVERLAP,0); + rPoolDefaults[SCHATTR_BAR_GAPWIDTH - SCHATTR_START] = new SfxInt32Item(SCHATTR_BAR_GAPWIDTH,0); + rPoolDefaults[SCHATTR_BAR_CONNECT - SCHATTR_START] = new SfxBoolItem(SCHATTR_BAR_CONNECT, false); + rPoolDefaults[SCHATTR_NUM_OF_LINES_FOR_BAR - SCHATTR_START] = new SfxInt32Item( SCHATTR_NUM_OF_LINES_FOR_BAR, 0 ); + rPoolDefaults[SCHATTR_SPLINE_ORDER - SCHATTR_START] = new SfxInt32Item( SCHATTR_SPLINE_ORDER, 3 ); + rPoolDefaults[SCHATTR_SPLINE_RESOLUTION - SCHATTR_START] = new SfxInt32Item( SCHATTR_SPLINE_RESOLUTION, 20 ); + rPoolDefaults[SCHATTR_GROUP_BARS_PER_AXIS - SCHATTR_START] = new SfxBoolItem(SCHATTR_GROUP_BARS_PER_AXIS, false); + rPoolDefaults[SCHATTR_STARTING_ANGLE - SCHATTR_START] = new SfxInt32Item( SCHATTR_STARTING_ANGLE, 90 ); + rPoolDefaults[SCHATTR_CLOCKWISE - SCHATTR_START] = new SfxBoolItem( SCHATTR_CLOCKWISE, false ); + + rPoolDefaults[SCHATTR_MISSING_VALUE_TREATMENT - SCHATTR_START] = new SfxInt32Item(SCHATTR_MISSING_VALUE_TREATMENT, 0); + rPoolDefaults[SCHATTR_AVAILABLE_MISSING_VALUE_TREATMENTS - SCHATTR_START] = new SfxIntegerListItem(SCHATTR_AVAILABLE_MISSING_VALUE_TREATMENTS, std::vector < sal_Int32 >() ); + rPoolDefaults[SCHATTR_INCLUDE_HIDDEN_CELLS - SCHATTR_START] = new SfxBoolItem(SCHATTR_INCLUDE_HIDDEN_CELLS, true); + rPoolDefaults[SCHATTR_HIDE_LEGEND_ENTRY - SCHATTR_START] = new SfxBoolItem(SCHATTR_HIDE_LEGEND_ENTRY, false); + + rPoolDefaults[SCHATTR_AXIS_FOR_ALL_SERIES - SCHATTR_START] = new SfxInt32Item(SCHATTR_AXIS_FOR_ALL_SERIES, 0); + + rPoolDefaults[SCHATTR_REGRESSION_TYPE - SCHATTR_START] = new SvxChartRegressItem (SvxChartRegress::NONE, SCHATTR_REGRESSION_TYPE); + rPoolDefaults[SCHATTR_REGRESSION_SHOW_EQUATION - SCHATTR_START] = new SfxBoolItem(SCHATTR_REGRESSION_SHOW_EQUATION, false); + rPoolDefaults[SCHATTR_REGRESSION_SHOW_COEFF - SCHATTR_START] = new SfxBoolItem(SCHATTR_REGRESSION_SHOW_COEFF, false); + rPoolDefaults[SCHATTR_REGRESSION_DEGREE - SCHATTR_START] = new SfxInt32Item(SCHATTR_REGRESSION_DEGREE, 2); + rPoolDefaults[SCHATTR_REGRESSION_PERIOD - SCHATTR_START] = new SfxInt32Item(SCHATTR_REGRESSION_PERIOD, 2); + rPoolDefaults[SCHATTR_REGRESSION_EXTRAPOLATE_FORWARD - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_REGRESSION_EXTRAPOLATE_FORWARD); + rPoolDefaults[SCHATTR_REGRESSION_EXTRAPOLATE_BACKWARD - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_REGRESSION_EXTRAPOLATE_BACKWARD); + rPoolDefaults[SCHATTR_REGRESSION_SET_INTERCEPT - SCHATTR_START] = new SfxBoolItem(SCHATTR_REGRESSION_SET_INTERCEPT, false); + rPoolDefaults[SCHATTR_REGRESSION_INTERCEPT_VALUE - SCHATTR_START] = new SvxDoubleItem(0.0, SCHATTR_REGRESSION_INTERCEPT_VALUE); + rPoolDefaults[SCHATTR_REGRESSION_CURVE_NAME - SCHATTR_START] = new SfxStringItem(SCHATTR_REGRESSION_CURVE_NAME, OUString()); + rPoolDefaults[SCHATTR_REGRESSION_XNAME - SCHATTR_START] = new SfxStringItem(SCHATTR_REGRESSION_XNAME, "x"); + rPoolDefaults[SCHATTR_REGRESSION_YNAME - SCHATTR_START] = new SfxStringItem(SCHATTR_REGRESSION_YNAME, "f(x)"); + + /************************************************************************** + * ItemInfos + **************************************************************************/ + const sal_uInt16 nMax = SCHATTR_END - SCHATTR_START + 1; + for( sal_uInt16 i = 0; i < nMax; i++ ) + { + pItemInfos[i]._nSID = 0; + pItemInfos[i]._bPoolable = true; + } + + // slot ids differing from which ids + pItemInfos[SCHATTR_SYMBOL_BRUSH - SCHATTR_START]._nSID = SID_ATTR_BRUSH; + pItemInfos[SCHATTR_STYLE_SYMBOL - SCHATTR_START]._nSID = SID_ATTR_SYMBOLTYPE; + pItemInfos[SCHATTR_SYMBOL_SIZE - SCHATTR_START]._nSID = SID_ATTR_SYMBOLSIZE; + + SetDefaults(ppPoolDefaults); + SetItemInfos(pItemInfos.get()); +} + +ChartItemPool::ChartItemPool(const ChartItemPool& rPool): + SfxItemPool(rPool) +{ +} + +ChartItemPool::~ChartItemPool() +{ + Delete(); + // release and delete static pool default items + ReleaseDefaults(true); +} + +SfxItemPool* ChartItemPool::Clone() const +{ + return new ChartItemPool(*this); +} + +MapUnit ChartItemPool::GetMetric(sal_uInt16 /* nWhich */) const +{ + return MapUnit::Map100thMM; +} + +SfxItemPool* ChartItemPool::CreateChartItemPool() +{ + return new ChartItemPool(); +} + +} // namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/ChartItemPool.hxx b/chart2/source/view/main/ChartItemPool.hxx new file mode 100644 index 000000000..b311b3050 --- /dev/null +++ b/chart2/source/view/main/ChartItemPool.hxx @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_CHART2_SOURCE_VIEW_MAIN_CHARTITEMPOOL_HXX +#define INCLUDED_CHART2_SOURCE_VIEW_MAIN_CHARTITEMPOOL_HXX + +#include <tools/mapunit.hxx> +#include <svl/itempool.hxx> +#include <memory> + +namespace chart +{ +class ChartItemPool : public SfxItemPool +{ +private: + std::unique_ptr<SfxItemInfo[]> pItemInfos; + +public: + ChartItemPool(); + ChartItemPool(const ChartItemPool& rPool); +protected: + virtual ~ChartItemPool() override; +public: + + virtual SfxItemPool* Clone() const override; + MapUnit GetMetric( sal_uInt16 nWhich ) const override; + + /// creates a pure chart item pool + static SfxItemPool* CreateChartItemPool(); +}; + +} // namespace chart + +#endif +// INCLUDED_CHART2_SOURCE_VIEW_MAIN_CHARTITEMPOOL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/ChartView.cxx b/chart2/source/view/main/ChartView.cxx new file mode 100644 index 000000000..4b6b1fa9b --- /dev/null +++ b/chart2/source/view/main/ChartView.cxx @@ -0,0 +1,3101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_feature_desktop.h> + +#include <ChartView.hxx> +#include <chartview/DrawModelWrapper.hxx> +#include <NumberFormatterWrapper.hxx> +#include <VDiagram.hxx> +#include "VTitle.hxx" +#include "VButton.hxx" +#include <ShapeFactory.hxx> +#include <VCoordinateSystem.hxx> +#include <VSeriesPlotter.hxx> +#include <CommonConverters.hxx> +#include <TitleHelper.hxx> +#include <LegendHelper.hxx> +#include "VLegend.hxx" +#include <PropertyMapper.hxx> +#include <ChartModel.hxx> +#include <ChartTypeHelper.hxx> +#include <ScaleAutomatism.hxx> +#include <ObjectIdentifier.hxx> +#include <DiagramHelper.hxx> +#include <RelativePositionHelper.hxx> +#include <servicenames.hxx> +#include <AxisHelper.hxx> +#include <AxisIndexDefines.hxx> +#include <BaseGFXHelper.hxx> +#include <DataSeriesHelper.hxx> +#include <DateHelper.hxx> +#include <ExplicitCategoriesProvider.hxx> +#include <defines.hxx> +#include <unonames.hxx> +#include <editeng/frmdiritem.hxx> +#include <tools/globname.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/scopeguard.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/math.hxx> +#include <unotools/streamwrap.hxx> +#include <svx/svdpage.hxx> +#include <svx/unopage.hxx> +#include <svx/unoshape.hxx> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <svx/unofill.hxx> +#include <drawinglayer/XShapeDumper.hxx> + +#include <time.h> + +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/chart/ChartAxisPosition.hpp> +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart2/AxisType.hpp> +#include <com/sun/star/chart2/StackingDirection.hpp> +#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> +#include <com/sun/star/chart2/XChartTypeContainer.hpp> +#include <com/sun/star/chart2/XDataSeriesContainer.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp> +#include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp> +#include <com/sun/star/drawing/GraphicExportFilter.hpp> +#include <com/sun/star/drawing/XShapeGroup.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/util/XRefreshable.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/XTextEmbeddedObjectsSupplier.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <svl/itempool.hxx> +#include <svl/languageoptions.hxx> +#include <comphelper/classids.hxx> +#include <servicenames_charttypes.hxx> + + +#include <rtl/ustring.hxx> + +#include <tools/diagnose_ex.h> +#include <tools/stream.hxx> + +#include <memory> +namespace com::sun::star::chart2 { class XChartDocument; } + +namespace chart { + +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Any; + +namespace { + +class theExplicitValueProviderUnoTunnelId : public rtl::Static<UnoTunnelIdInit, theExplicitValueProviderUnoTunnelId> {}; + +typedef std::pair< sal_Int32, sal_Int32 > tFullAxisIndex; //first index is the dimension, second index is the axis index that indicates whether this is a main or secondary axis +typedef std::map< VCoordinateSystem*, tFullAxisIndex > tCoordinateSystemMap; + +/** This class handles a collection of coordinate systems and is used for + * executing some action on all coordinate systems such as + * `prepareAutomaticAxisScaling` and `setExplicitScaleAndIncrement`. + * Moreover it contains the `aAutoScaling` object that is an instance of + * the `ScaleAutomatism` class. The initialization of `aAutoScaling` is + * performed in the `SeriesPlotterContainer::initAxisUsageList` method and is + * used in the `SeriesPlotterContainer::doAutoScaling` for calculating explicit + * scale and increment objects (see `SeriesPlotterContainer::doAutoScaling`). + */ +struct AxisUsage +{ + AxisUsage(); + ~AxisUsage(); + + void addCoordinateSystem( VCoordinateSystem* pCooSys, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ); + std::vector< VCoordinateSystem* > getCoordinateSystems( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ); + sal_Int32 getMaxAxisIndexForDimension( sal_Int32 nDimensionIndex ); + + void prepareAutomaticAxisScaling( ScaleAutomatism& rScaleAutomatism, sal_Int32 nDimIndex, sal_Int32 nAxisIndex ); + void setExplicitScaleAndIncrement( sal_Int32 nDimIndex, sal_Int32 nAxisIndex, const ExplicitScaleData& rScale, const ExplicitIncrementData& rInc ); + + ScaleAutomatism aAutoScaling; + +private: + tCoordinateSystemMap aCoordinateSystems; + std::map< sal_Int32, sal_Int32 > aMaxIndexPerDimension; +}; + +AxisUsage::AxisUsage() + : aAutoScaling(AxisHelper::createDefaultScale(), Date(Date::SYSTEM)) +{ +} + +AxisUsage::~AxisUsage() +{ + aCoordinateSystems.clear(); +} + +void AxisUsage::addCoordinateSystem( VCoordinateSystem* pCooSys, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) +{ + if(!pCooSys) + return; + + tFullAxisIndex aFullAxisIndex( nDimensionIndex, nAxisIndex ); + tCoordinateSystemMap::const_iterator aFound( aCoordinateSystems.find(pCooSys) ); + + //use one scale only once for each coordinate system + //main axis are preferred over secondary axis + //value scales are preferred + if(aFound!=aCoordinateSystems.end()) + { + sal_Int32 nFoundAxisIndex = aFound->second.second; + if( nFoundAxisIndex < nAxisIndex ) + return; + sal_Int32 nFoundDimension = aFound->second.first; + if( nFoundDimension ==1 ) + return; + if( nFoundDimension < nDimensionIndex ) + return; + } + aCoordinateSystems[pCooSys] = aFullAxisIndex; + + //set maximum scale index + std::map< sal_Int32, sal_Int32 >::const_iterator aIter = aMaxIndexPerDimension.find(nDimensionIndex); + if( aIter != aMaxIndexPerDimension.end() ) + { + sal_Int32 nCurrentMaxIndex = aIter->second; + if( nCurrentMaxIndex < nAxisIndex ) + aMaxIndexPerDimension[nDimensionIndex]=nAxisIndex; + } + else + aMaxIndexPerDimension[nDimensionIndex]=nAxisIndex; +} + +std::vector< VCoordinateSystem* > AxisUsage::getCoordinateSystems( sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) +{ + std::vector< VCoordinateSystem* > aRet; + + for (auto const& coordinateSystem : aCoordinateSystems) + { + if( coordinateSystem.second.first != nDimensionIndex ) + continue; + if( coordinateSystem.second.second != nAxisIndex ) + continue; + aRet.push_back( coordinateSystem.first ); + } + + return aRet; +} + +sal_Int32 AxisUsage::getMaxAxisIndexForDimension( sal_Int32 nDimensionIndex ) +{ + sal_Int32 nRet = -1; + std::map< sal_Int32, sal_Int32 >::const_iterator aIter = aMaxIndexPerDimension.find(nDimensionIndex); + if( aIter != aMaxIndexPerDimension.end() ) + nRet = aIter->second; + return nRet; +} + +void AxisUsage::prepareAutomaticAxisScaling( ScaleAutomatism& rScaleAutomatism, sal_Int32 nDimIndex, sal_Int32 nAxisIndex ) +{ + std::vector<VCoordinateSystem*> aVCooSysList = getCoordinateSystems(nDimIndex, nAxisIndex); + for (VCoordinateSystem * i : aVCooSysList) + i->prepareAutomaticAxisScaling(rScaleAutomatism, nDimIndex, nAxisIndex); +} + +void AxisUsage::setExplicitScaleAndIncrement( + sal_Int32 nDimIndex, sal_Int32 nAxisIndex, const ExplicitScaleData& rScale, const ExplicitIncrementData& rInc ) +{ + std::vector<VCoordinateSystem*> aVCooSysList = getCoordinateSystems(nDimIndex, nAxisIndex); + for (VCoordinateSystem* i : aVCooSysList) + i->setExplicitScaleAndIncrement(nDimIndex, nAxisIndex, rScale, rInc); +} + +typedef std::vector<std::unique_ptr<VSeriesPlotter> > SeriesPlottersType; + +/** This class is a container of `SeriesPlotter` objects (such as `PieChart` + * instances). It is used for initializing coordinate systems, axes and scales + * of all series plotters which belongs to the container. + */ +class SeriesPlotterContainer +{ +public: + explicit SeriesPlotterContainer( std::vector< std::unique_ptr<VCoordinateSystem> >& rVCooSysList ); + ~SeriesPlotterContainer(); + + /** It is used to set coordinate systems (`m_rVCooSysList`), this method + * is invoked by `ChartView::createShapes2D` before of + * `ChartView::impl_createDiagramAndContent`. + * Coordinate systems are retrieved through the `XCoordinateSystemContainer` + * interface implemented by a diagram object which is provided by the + * `ChartModel` object passed to the method (`rChartModel.getFirstDiagram()`). + * + * It is used for creating series plotters and appending them + * to `m_aSeriesPlotterList`. The created series plotters are initialized + * through data (number formats supplier, color scheme, data series), + * extracted from the chart model or the diagram objects. An exception is + * the explicit category provider that is retrieved through the + * `VCoordinateSystem` object used by the series plotter. + * + * It sets the minimum-maximum supplier for a coordinate system: + * this supplier is the series plotter itself which utilizes the given + * coordinate system. In fact `VSeriesPlotter` has `MinimumMaximumSupplier` + * as one of its base classes. + * Hence, for instance, a `PieChart`, which is a series plotter, is + * a `MinimumMaximumSupplier`, too. + */ + void initializeCooSysAndSeriesPlotter( ChartModel& rModel ); + + /** This method is invoked by `ChartView::impl_createDiagramAndContent`. + * It iterates on every axis of every coordinate systems, and if the axis + * is not yet present in `m_aAxisUsageList` it creates a new `AxisUsage` + * object and initialize its `aAutoScaling` member to the `ScaleData` + * object of the current axis. + */ + void initAxisUsageList(const Date& rNullDate); + + /** + * Perform automatic axis scaling and determine the amount and spacing of + * increments. It assumes that the caller has determined the size of the + * largest axis label text object prior to calling this method. + * + * The new axis scaling data will be stored in the VCoordinateSystem + * objects. The label alignment direction for each axis will also get + * determined during this process, and stored in VAxis. + * + * This method is invoked by `ChartView::impl_createDiagramAndContent` + * soon after `initAxisUsageList`. + * It initializes explicit scale and increment objects for all coordinate + * systems in `m_rVCooSysList`. + * This action is achieved by iterating on the `m_aAxisUsageList` container, + * and performing 3 steps: + * 1- call `VCoordinateSystem::prepareAutomaticAxisScaling` for setting + * scaling parameters of the `aAutoScaling` member (a `ScaleAutomatism` + * object) for the current `AxisUsage` instance + * (see `VCoordinateSystem::prepareAutomaticAxisScaling`); + * 2- calculate the explicit scale and increment objects + * (see ScaleAutomatism::calculateExplicitScaleAndIncrement); + * 3- set the explicit scale and increment objects for each coordinate + * system. + */ + void doAutoScaling( ChartModel& rModel ); + + /** + * After auto-scaling is performed, call this method to set the explicit + * scaling and increment data to all relevant VAxis objects. + */ + void updateScalesAndIncrementsOnAxes(); + + /** + * After auto-scaling is performed, call this method to set the explicit + * scaling data to all the plotters. + */ + void setScalesFromCooSysToPlotter(); + + void setNumberFormatsFromAxes(); + drawing::Direction3D getPreferredAspectRatio(); + + SeriesPlottersType& getSeriesPlotterList() { return m_aSeriesPlotterList; } + std::vector< std::unique_ptr<VCoordinateSystem> >& getCooSysList() { return m_rVCooSysList; } + std::vector< LegendEntryProvider* > getLegendEntryProviderList(); + + void AdaptScaleOfYAxisWithoutAttachedSeries( ChartModel& rModel ); + + static bool isCategoryPositionShifted( + const chart2::ScaleData& rSourceScale, bool bHasComplexCategories ); + +private: + /** A vector of series plotters. + */ + SeriesPlottersType m_aSeriesPlotterList; + + /** A vector of coordinate systems. + */ + std::vector< std::unique_ptr<VCoordinateSystem> >& m_rVCooSysList; + + /** A map whose key is a `XAxis` interface and the related value is + * an object of `AxisUsage` type. + */ + std::map< uno::Reference< XAxis >, AxisUsage > m_aAxisUsageList; + + /** + * Max axis index of all dimensions. Currently this can be either 0 or 1 + * since we only support primary and secondary axes per dimension. The + * value of 0 means all dimensions have only primary axis, while 1 means + * at least one dimension has a secondary axis. + */ + sal_Int32 m_nMaxAxisIndex; + + sal_Int32 m_nDefaultDateNumberFormat; +}; + +SeriesPlotterContainer::SeriesPlotterContainer( std::vector< std::unique_ptr<VCoordinateSystem> >& rVCooSysList ) + : m_rVCooSysList( rVCooSysList ) + , m_nMaxAxisIndex(0) + , m_nDefaultDateNumberFormat(0) +{ +} + +SeriesPlotterContainer::~SeriesPlotterContainer() +{ + // - remove plotter from coordinatesystems + for(auto & nC : m_rVCooSysList) + nC->clearMinimumAndMaximumSupplierList(); +} + +std::vector< LegendEntryProvider* > SeriesPlotterContainer::getLegendEntryProviderList() +{ + std::vector< LegendEntryProvider* > aRet( m_aSeriesPlotterList.size() ); + sal_Int32 nN = 0; + for( const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList) + aRet[nN++] = aPlotter.get(); + return aRet; +} + +VCoordinateSystem* findInCooSysList( const std::vector< std::unique_ptr<VCoordinateSystem> >& rVCooSysList + , const uno::Reference< XCoordinateSystem >& xCooSys ) +{ + for(auto & pVCooSys : rVCooSysList) + { + if(pVCooSys->getModel()==xCooSys) + return pVCooSys.get(); + } + return nullptr; +} + +VCoordinateSystem* lcl_getCooSysForPlotter( const std::vector< std::unique_ptr<VCoordinateSystem> >& rVCooSysList, MinimumAndMaximumSupplier* pMinimumAndMaximumSupplier ) +{ + if(!pMinimumAndMaximumSupplier) + return nullptr; + for(auto & pVCooSys : rVCooSysList) + { + if(pVCooSys->hasMinimumAndMaximumSupplier( pMinimumAndMaximumSupplier )) + return pVCooSys.get(); + } + return nullptr; +} + +VCoordinateSystem* addCooSysToList( std::vector< std::unique_ptr<VCoordinateSystem> >& rVCooSysList + , const uno::Reference< XCoordinateSystem >& xCooSys + , ChartModel& rChartModel ) +{ + VCoordinateSystem* pExistingVCooSys = findInCooSysList( rVCooSysList, xCooSys ); + if( pExistingVCooSys ) + return pExistingVCooSys; + + std::unique_ptr<VCoordinateSystem> pVCooSys = VCoordinateSystem::createCoordinateSystem(xCooSys ); + if(!pVCooSys) + return nullptr; + + OUString aCooSysParticle( ObjectIdentifier::createParticleForCoordinateSystem( xCooSys, rChartModel ) ); + pVCooSys->setParticle(aCooSysParticle); + + pVCooSys->setExplicitCategoriesProvider( new ExplicitCategoriesProvider(xCooSys, rChartModel) ); + rVCooSysList.push_back( std::move(pVCooSys) ); + return rVCooSysList.back().get(); +} + +void SeriesPlotterContainer::initializeCooSysAndSeriesPlotter( + ChartModel& rChartModel ) +{ + uno::Reference< XDiagram > xDiagram( rChartModel.getFirstDiagram() ); + if( !xDiagram.is()) + return; + + uno::Reference< util::XNumberFormatsSupplier > xNumberFormatsSupplier( static_cast< ::cppu::OWeakObject* >( &rChartModel ), uno::UNO_QUERY ); + if( rChartModel.hasInternalDataProvider() && DiagramHelper::isSupportingDateAxis( xDiagram ) ) + m_nDefaultDateNumberFormat=DiagramHelper::getDateNumberFormat( xNumberFormatsSupplier ); + + sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram ); + if(!nDimensionCount) + { + //@todo handle mixed dimension + nDimensionCount = 2; + } + + bool bSortByXValues = false; + bool bConnectBars = false; + bool bGroupBarsPerAxis = true; + bool bIncludeHiddenCells = true; + bool bSecondaryYaxisVisible = true; + sal_Int32 nStartingAngle = 90; + sal_Int32 n3DRelativeHeight = 100; + try + { + uno::Reference< beans::XPropertySet > xDiaProp( xDiagram, uno::UNO_QUERY_THROW ); + xDiaProp->getPropertyValue(CHART_UNONAME_SORT_BY_XVALUES) >>= bSortByXValues; + xDiaProp->getPropertyValue( "ConnectBars" ) >>= bConnectBars; + xDiaProp->getPropertyValue( "GroupBarsPerAxis" ) >>= bGroupBarsPerAxis; + xDiaProp->getPropertyValue( "IncludeHiddenCells" ) >>= bIncludeHiddenCells; + xDiaProp->getPropertyValue( "StartingAngle" ) >>= nStartingAngle; + + if (nDimensionCount == 3) + { + xDiaProp->getPropertyValue( "3DRelativeHeight" ) >>= n3DRelativeHeight; + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + } + + //prepare for autoscaling and shape creation + // - create plotter for charttypes (for each first scale group at each plotter, as they are independent) + // - add series to plotter (thus each charttype can provide minimum and maximum values for autoscaling) + // - add plotter to coordinate systems + + //iterate through all coordinate systems + uno::Reference< XCoordinateSystemContainer > xCooSysContainer( xDiagram, uno::UNO_QUERY ); + OSL_ASSERT( xCooSysContainer.is()); + if( !xCooSysContainer.is()) + return; + uno::Reference< XColorScheme > xColorScheme( xDiagram->getDefaultColorScheme()); + uno::Sequence< uno::Reference< XCoordinateSystem > > aCooSysList( xCooSysContainer->getCoordinateSystems() ); + sal_Int32 nGlobalSeriesIndex = 0;//for automatic symbols + for( sal_Int32 nCS = 0; nCS < aCooSysList.getLength(); ++nCS ) + { + uno::Reference< XCoordinateSystem > xCooSys( aCooSysList[nCS] ); + VCoordinateSystem* pVCooSys = addCooSysToList(m_rVCooSysList,xCooSys,rChartModel); + // Let's check whether the secondary Y axis is visible + try + { + if (xCooSys->getMaximumAxisIndexByDimension(1) > 0) + { + Reference< beans::XPropertySet > xAxisProp(xCooSys->getAxisByDimension(1, 1), uno::UNO_QUERY); + xAxisProp->getPropertyValue("Show") >>= bSecondaryYaxisVisible; + } + } + catch (const lang::IndexOutOfBoundsException&) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + //iterate through all chart types in the current coordinate system + uno::Reference< XChartTypeContainer > xChartTypeContainer( xCooSys, uno::UNO_QUERY ); + OSL_ASSERT( xChartTypeContainer.is()); + if( !xChartTypeContainer.is() ) + continue; + uno::Sequence< uno::Reference< XChartType > > aChartTypeList( xChartTypeContainer->getChartTypes() ); + for( sal_Int32 nT = 0; nT < aChartTypeList.getLength(); ++nT ) + { + uno::Reference< XChartType > xChartType( aChartTypeList[nT] ); + if(nDimensionCount == 3 && xChartType->getChartType().equalsIgnoreAsciiCase(CHART2_SERVICE_NAME_CHARTTYPE_PIE)) + { + uno::Reference< beans::XPropertySet > xPropertySet( xChartType, uno::UNO_QUERY ); + if (xPropertySet.is()) + { + try + { + sal_Int32 n3DRelativeHeightOldValue(100); + uno::Any aAny = xPropertySet->getPropertyValue( "3DRelativeHeight" ); + aAny >>= n3DRelativeHeightOldValue; + if (n3DRelativeHeightOldValue != n3DRelativeHeight) + xPropertySet->setPropertyValue( "3DRelativeHeight", uno::Any(n3DRelativeHeight) ); + } + catch (const uno::Exception&) { } + } + } + + bool bExcludingPositioning = DiagramHelper::getDiagramPositioningMode( xDiagram ) == DiagramPositioningMode_EXCLUDING; + VSeriesPlotter* pPlotter = VSeriesPlotter::createSeriesPlotter( xChartType, nDimensionCount, bExcludingPositioning ); + if( !pPlotter ) + continue; + + m_aSeriesPlotterList.push_back( std::unique_ptr<VSeriesPlotter>(pPlotter) ); + pPlotter->setNumberFormatsSupplier( xNumberFormatsSupplier ); + pPlotter->setColorScheme( xColorScheme ); + if(pVCooSys) + pPlotter->setExplicitCategoriesProvider( pVCooSys->getExplicitCategoriesProvider() ); + sal_Int32 nMissingValueTreatment = DiagramHelper::getCorrectedMissingValueTreatment( xDiagram, xChartType ); + + if(pVCooSys) + pVCooSys->addMinimumAndMaximumSupplier(pPlotter); + + uno::Reference< XDataSeriesContainer > xDataSeriesContainer( xChartType, uno::UNO_QUERY ); + OSL_ASSERT( xDataSeriesContainer.is()); + if( !xDataSeriesContainer.is() ) + continue; + + sal_Int32 zSlot=-1; + sal_Int32 xSlot=-1; + sal_Int32 ySlot=-1; + uno::Sequence< uno::Reference< XDataSeries > > aSeriesList( xDataSeriesContainer->getDataSeries() ); + for( sal_Int32 nS = 0; nS < aSeriesList.getLength(); ++nS ) + { + uno::Reference< XDataSeries > const & xDataSeries = aSeriesList[nS]; + if(!xDataSeries.is()) + continue; + if( !bIncludeHiddenCells && !DataSeriesHelper::hasUnhiddenData(xDataSeries) ) + continue; + + std::unique_ptr<VDataSeries> pSeries(new VDataSeries( xDataSeries )); + + pSeries->setGlobalSeriesIndex(nGlobalSeriesIndex); + nGlobalSeriesIndex++; + + if( bSortByXValues ) + pSeries->doSortByXValues(); + + pSeries->setConnectBars( bConnectBars ); + pSeries->setGroupBarsPerAxis( bGroupBarsPerAxis ); + pSeries->setStartingAngle( nStartingAngle ); + + pSeries->setMissingValueTreatment( nMissingValueTreatment ); + + OUString aSeriesParticle( ObjectIdentifier::createParticleForSeries( 0, nCS, nT, nS ) ); + pSeries->setParticle(aSeriesParticle); + + OUString aRole( ChartTypeHelper::getRoleOfSequenceForDataLabelNumberFormatDetection( xChartType ) ); + pSeries->setRoleOfSequenceForDataLabelNumberFormatDetection(aRole); + + //ignore secondary axis for charttypes that do not support them + if( pSeries->getAttachedAxisIndex() != MAIN_AXIS_INDEX && + ( !ChartTypeHelper::isSupportingSecondaryAxis( xChartType, nDimensionCount ) || + !bSecondaryYaxisVisible ) ) + { + pSeries->setAttachedAxisIndex(MAIN_AXIS_INDEX); + } + + StackingDirection eDirection = pSeries->getStackingDirection(); + switch(eDirection) + { + case StackingDirection_NO_STACKING: + xSlot++; ySlot=-1; + if(zSlot<0) + zSlot=0; + break; + case StackingDirection_Y_STACKING: + ySlot++; + if(xSlot<0) + xSlot=0; + if(zSlot<0) + zSlot=0; + break; + case StackingDirection_Z_STACKING: + zSlot++; xSlot=-1; ySlot=-1; + break; + default: + // UNO enums have one additional auto-generated case + break; + } + pPlotter->addSeries( std::move(pSeries), zSlot, xSlot, ySlot ); + } + } + } + + //transport seriesnames to the coordinatesystems if needed + if( m_aSeriesPlotterList.empty() ) + return; + + uno::Sequence< OUString > aSeriesNames; + bool bSeriesNamesInitialized = false; + for(auto & pVCooSys : m_rVCooSysList) + { + if( pVCooSys->needSeriesNamesForAxis() ) + { + if(!bSeriesNamesInitialized) + { + aSeriesNames = m_aSeriesPlotterList[0]->getSeriesNames(); + bSeriesNamesInitialized = true; + } + pVCooSys->setSeriesNamesForAxis( aSeriesNames ); + } + } +} + +bool SeriesPlotterContainer::isCategoryPositionShifted( + const chart2::ScaleData& rSourceScale, bool bHasComplexCategories ) +{ + if (rSourceScale.AxisType == AxisType::CATEGORY) + return bHasComplexCategories || rSourceScale.ShiftedCategoryPosition; + + if (rSourceScale.AxisType == AxisType::DATE) + return rSourceScale.ShiftedCategoryPosition; + + return rSourceScale.AxisType == AxisType::SERIES; +} + +void SeriesPlotterContainer::initAxisUsageList(const Date& rNullDate) +{ + m_aAxisUsageList.clear(); + + // Loop through coordinate systems in the diagram (though for now + // there should only be one coordinate system per diagram). + for (auto & pVCooSys : m_rVCooSysList) + { + uno::Reference<XCoordinateSystem> xCooSys = pVCooSys->getModel(); + sal_Int32 nDimCount = xCooSys->getDimension(); + bool bComplexCategoryAllowed = ChartTypeHelper::isSupportingComplexCategory(AxisHelper::getChartTypeByIndex(xCooSys, 0)); + + for (sal_Int32 nDimIndex = 0; nDimIndex < nDimCount; ++nDimIndex) + { + bool bDateAxisAllowed = ChartTypeHelper::isSupportingDateAxis( + AxisHelper::getChartTypeByIndex(xCooSys, 0), nDimIndex); + + // Each dimension may have primary and secondary axes. + const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nDimIndex); + for (sal_Int32 nAxisIndex = 0; nAxisIndex <= nMaxAxisIndex; ++nAxisIndex) + { + uno::Reference<XAxis> xAxis = xCooSys->getAxisByDimension(nDimIndex, nAxisIndex); + + if (!xAxis.is()) + continue; + + if (m_aAxisUsageList.find(xAxis) == m_aAxisUsageList.end()) + { + // Create axis usage object for this axis. + + chart2::ScaleData aSourceScale = xAxis->getScaleData(); + ExplicitCategoriesProvider* pCatProvider = pVCooSys->getExplicitCategoriesProvider(); + if (nDimIndex == 0) + AxisHelper::checkDateAxis( aSourceScale, pCatProvider, bDateAxisAllowed ); + + bool bHasComplexCat = pCatProvider && pCatProvider->hasComplexCategories() && bComplexCategoryAllowed; + aSourceScale.ShiftedCategoryPosition = isCategoryPositionShifted(aSourceScale, bHasComplexCat); + + m_aAxisUsageList[xAxis].aAutoScaling = ScaleAutomatism(aSourceScale, rNullDate); + } + + AxisUsage& rAxisUsage = m_aAxisUsageList[xAxis]; + rAxisUsage.addCoordinateSystem(pVCooSys.get(), nDimIndex, nAxisIndex); + } + } + } + + // Determine the highest axis index of all dimensions. + m_nMaxAxisIndex = 0; + for (const auto & pVCooSys : m_rVCooSysList) + { + uno::Reference<XCoordinateSystem> xCooSys = pVCooSys->getModel(); + sal_Int32 nDimCount = xCooSys->getDimension(); + + for (sal_Int32 nDimIndex = 0; nDimIndex < nDimCount; ++nDimIndex) + { + for (auto & axisUsage : m_aAxisUsageList) + { + sal_Int32 nLocalMax = axisUsage.second.getMaxAxisIndexForDimension(nDimIndex); + if (m_nMaxAxisIndex < nLocalMax) + m_nMaxAxisIndex = nLocalMax; + } + } + } +} + +void SeriesPlotterContainer::setScalesFromCooSysToPlotter() +{ + //set scales to plotter to enable them to provide the preferred scene AspectRatio + for( const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList ) + { + VSeriesPlotter* pSeriesPlotter = aPlotter.get(); + VCoordinateSystem* pVCooSys = lcl_getCooSysForPlotter( m_rVCooSysList, pSeriesPlotter ); + if(pVCooSys) + { + pSeriesPlotter->setScales( pVCooSys->getExplicitScales(0,0), pVCooSys->getPropertySwapXAndYAxis() ); + sal_Int32 nMaxAxisIndex = pVCooSys->getMaximumAxisIndexByDimension(1);//only additional value axis are relevant for series plotter + for( sal_Int32 nI=1; nI<=nMaxAxisIndex; nI++ ) + pSeriesPlotter->addSecondaryValueScale( pVCooSys->getExplicitScale(1,nI), nI ); + } + } +} + +void SeriesPlotterContainer::setNumberFormatsFromAxes() +{ + //set numberformats to plotter to enable them to display the data labels in the numberformat of the axis + for( const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList ) + { + VSeriesPlotter* pSeriesPlotter = aPlotter.get(); + VCoordinateSystem* pVCooSys = lcl_getCooSysForPlotter( m_rVCooSysList, pSeriesPlotter ); + if(pVCooSys) + { + AxesNumberFormats aAxesNumberFormats; + const uno::Reference< XCoordinateSystem >& xCooSys = pVCooSys->getModel(); + sal_Int32 nDimensionCount = xCooSys->getDimension(); + for(sal_Int32 nDimensionIndex=0; nDimensionIndex<nDimensionCount; ++nDimensionIndex) + { + const sal_Int32 nMaximumAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex); + for(sal_Int32 nAxisIndex=0; nAxisIndex<=nMaximumAxisIndex; ++nAxisIndex) + { + try + { + Reference< beans::XPropertySet > xAxisProp( xCooSys->getAxisByDimension( nDimensionIndex, nAxisIndex ), uno::UNO_QUERY ); + if( xAxisProp.is()) + { + sal_Int32 nNumberFormatKey(0); + if( xAxisProp->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nNumberFormatKey ) + { + aAxesNumberFormats.setFormat( nNumberFormatKey, nDimensionIndex, nAxisIndex ); + } + else if( nDimensionIndex==0 ) + { + //provide a default date format for date axis with own data + aAxesNumberFormats.setFormat( m_nDefaultDateNumberFormat, nDimensionIndex, nAxisIndex ); + } + } + } + catch( const lang::IndexOutOfBoundsException& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } + } + } +} + +void SeriesPlotterContainer::updateScalesAndIncrementsOnAxes() +{ + for(auto & nC : m_rVCooSysList) + nC->updateScalesAndIncrementsOnAxes(); +} + +void SeriesPlotterContainer::doAutoScaling( ChartModel& rChartModel ) +{ + if (m_aSeriesPlotterList.empty() || m_aAxisUsageList.empty()) + // We need these two containers populated to do auto-scaling. Bail out. + return; + + //iterate over the main scales first than secondary axis + for (sal_Int32 nAxisIndex = 0; nAxisIndex <= m_nMaxAxisIndex; ++nAxisIndex) + { + // - first do autoscale for all x and z scales (because they are treated independent) + for (auto & axisUsage : m_aAxisUsageList) + { + AxisUsage& rAxisUsage = axisUsage.second; + + rAxisUsage.prepareAutomaticAxisScaling(rAxisUsage.aAutoScaling, 0, nAxisIndex); + rAxisUsage.prepareAutomaticAxisScaling(rAxisUsage.aAutoScaling, 2, nAxisIndex); + + ExplicitScaleData aExplicitScale; + ExplicitIncrementData aExplicitIncrement; + rAxisUsage.aAutoScaling.calculateExplicitScaleAndIncrement( aExplicitScale, aExplicitIncrement ); + + rAxisUsage.setExplicitScaleAndIncrement(0, nAxisIndex, aExplicitScale, aExplicitIncrement); + rAxisUsage.setExplicitScaleAndIncrement(2, nAxisIndex, aExplicitScale, aExplicitIncrement); + } + + // - second do autoscale for the dependent y scales (the coordinate systems are prepared with x and z scales already ) + for (auto & axisUsage : m_aAxisUsageList) + { + AxisUsage& rAxisUsage = axisUsage.second; + + rAxisUsage.prepareAutomaticAxisScaling(rAxisUsage.aAutoScaling, 1, nAxisIndex); + + ExplicitScaleData aExplicitScale; + ExplicitIncrementData aExplicitIncrement; + rAxisUsage.aAutoScaling.calculateExplicitScaleAndIncrement( aExplicitScale, aExplicitIncrement ); + + rAxisUsage.setExplicitScaleAndIncrement(0, nAxisIndex, aExplicitScale, aExplicitIncrement); + rAxisUsage.setExplicitScaleAndIncrement(1, nAxisIndex, aExplicitScale, aExplicitIncrement); + rAxisUsage.setExplicitScaleAndIncrement(2, nAxisIndex, aExplicitScale, aExplicitIncrement); + } + } + AdaptScaleOfYAxisWithoutAttachedSeries( rChartModel ); +} + +void SeriesPlotterContainer::AdaptScaleOfYAxisWithoutAttachedSeries( ChartModel& rModel ) +{ + //issue #i80518# + for( sal_Int32 nAxisIndex=0; nAxisIndex<=m_nMaxAxisIndex; nAxisIndex++ ) + { + for (auto & axisUsage : m_aAxisUsageList) + { + AxisUsage& rAxisUsage = axisUsage.second; + std::vector< VCoordinateSystem* > aVCooSysList_Y = rAxisUsage.getCoordinateSystems( 1, nAxisIndex ); + if( aVCooSysList_Y.empty() ) + continue; + + uno::Reference< XDiagram > xDiagram( rModel.getFirstDiagram() ); + if (!xDiagram.is()) + continue; + + bool bSeriesAttachedToThisAxis = false; + sal_Int32 nAttachedAxisIndex = -1; + { + std::vector< Reference< XDataSeries > > aSeriesVector( DiagramHelper::getDataSeriesFromDiagram( xDiagram ) ); + for (auto const& series : aSeriesVector) + { + sal_Int32 nCurrentIndex = DataSeriesHelper::getAttachedAxisIndex(series); + if( nAxisIndex == nCurrentIndex ) + { + bSeriesAttachedToThisAxis = true; + break; + } + else if( nAttachedAxisIndex<0 || nAttachedAxisIndex>nCurrentIndex ) + nAttachedAxisIndex=nCurrentIndex; + } + } + + if (bSeriesAttachedToThisAxis || nAttachedAxisIndex < 0) + continue; + + for(VCoordinateSystem* nC : aVCooSysList_Y) + { + nC->prepareAutomaticAxisScaling( rAxisUsage.aAutoScaling, 1, nAttachedAxisIndex ); + + ExplicitScaleData aExplicitScaleSource = nC->getExplicitScale( 1,nAttachedAxisIndex ); + ExplicitIncrementData aExplicitIncrementSource = nC->getExplicitIncrement( 1,nAttachedAxisIndex ); + + ExplicitScaleData aExplicitScaleDest = nC->getExplicitScale( 1,nAxisIndex ); + ExplicitIncrementData aExplicitIncrementDest = nC->getExplicitIncrement( 1,nAxisIndex ); + + aExplicitScaleDest.Orientation = aExplicitScaleSource.Orientation; + aExplicitScaleDest.Scaling = aExplicitScaleSource.Scaling; + aExplicitScaleDest.AxisType = aExplicitScaleSource.AxisType; + + aExplicitIncrementDest.BaseValue = aExplicitIncrementSource.BaseValue; + + ScaleData aScale( rAxisUsage.aAutoScaling.getScale() ); + if( !aScale.Minimum.hasValue() ) + { + bool bNewMinOK = true; + double fMax=0.0; + if( aScale.Maximum >>= fMax ) + bNewMinOK = (aExplicitScaleSource.Minimum <= fMax); + if( bNewMinOK ) + aExplicitScaleDest.Minimum = aExplicitScaleSource.Minimum; + } + else + aExplicitIncrementDest.BaseValue = aExplicitScaleDest.Minimum; + + if( !aScale.Maximum.hasValue() ) + { + bool bNewMaxOK = true; + double fMin=0.0; + if( aScale.Minimum >>= fMin ) + bNewMaxOK = (fMin <= aExplicitScaleSource.Maximum); + if( bNewMaxOK ) + aExplicitScaleDest.Maximum = aExplicitScaleSource.Maximum; + } + if( !aScale.Origin.hasValue() ) + aExplicitScaleDest.Origin = aExplicitScaleSource.Origin; + + if( !aScale.IncrementData.Distance.hasValue() ) + aExplicitIncrementDest.Distance = aExplicitIncrementSource.Distance; + + bool bAutoMinorInterval = true; + if( aScale.IncrementData.SubIncrements.hasElements() ) + bAutoMinorInterval = !( aScale.IncrementData.SubIncrements[0].IntervalCount.hasValue() ); + if( bAutoMinorInterval ) + { + if( !aExplicitIncrementDest.SubIncrements.empty() && !aExplicitIncrementSource.SubIncrements.empty() ) + aExplicitIncrementDest.SubIncrements[0].IntervalCount = + aExplicitIncrementSource.SubIncrements[0].IntervalCount; + } + + nC->setExplicitScaleAndIncrement( 1, nAxisIndex, aExplicitScaleDest, aExplicitIncrementDest ); + } + } + } + + if( !AxisHelper::isAxisPositioningEnabled() ) + return; + + //correct origin for y main axis (the origin is where the other main axis crosses) + sal_Int32 nAxisIndex=0; + sal_Int32 nDimensionIndex=1; + for (auto & axisUsage : m_aAxisUsageList) + { + AxisUsage& rAxisUsage = axisUsage.second; + std::vector< VCoordinateSystem* > aVCooSysList = rAxisUsage.getCoordinateSystems(nDimensionIndex,nAxisIndex); + size_t nC; + for( nC=0; nC < aVCooSysList.size(); nC++) + { + ExplicitScaleData aExplicitScale( aVCooSysList[nC]->getExplicitScale( nDimensionIndex, nAxisIndex ) ); + ExplicitIncrementData aExplicitIncrement( aVCooSysList[nC]->getExplicitIncrement( nDimensionIndex, nAxisIndex ) ); + + Reference< chart2::XCoordinateSystem > xCooSys( aVCooSysList[nC]->getModel() ); + Reference< XAxis > xAxis( xCooSys->getAxisByDimension( nDimensionIndex, nAxisIndex ) ); + Reference< beans::XPropertySet > xCrossingMainAxis( AxisHelper::getCrossingMainAxis( xAxis, xCooSys ), uno::UNO_QUERY ); + + css::chart::ChartAxisPosition eCrossingMainAxisPos( css::chart::ChartAxisPosition_ZERO ); + if( xCrossingMainAxis.is() ) + { + xCrossingMainAxis->getPropertyValue("CrossoverPosition") >>= eCrossingMainAxisPos; + if( eCrossingMainAxisPos == css::chart::ChartAxisPosition_VALUE ) + { + double fValue = 0.0; + xCrossingMainAxis->getPropertyValue("CrossoverValue") >>= fValue; + aExplicitScale.Origin = fValue; + } + else if( eCrossingMainAxisPos == css::chart::ChartAxisPosition_ZERO ) + aExplicitScale.Origin = 0.0; + else if( eCrossingMainAxisPos == css::chart::ChartAxisPosition_START ) + aExplicitScale.Origin = aExplicitScale.Minimum; + else if( eCrossingMainAxisPos == css::chart::ChartAxisPosition_END ) + aExplicitScale.Origin = aExplicitScale.Maximum; + } + + aVCooSysList[nC]->setExplicitScaleAndIncrement( nDimensionIndex, nAxisIndex, aExplicitScale, aExplicitIncrement ); + } + } +} + +drawing::Direction3D SeriesPlotterContainer::getPreferredAspectRatio() +{ + drawing::Direction3D aPreferredAspectRatio(1.0,1.0,1.0); + + //get a list of all preferred aspect ratios and combine them + //first with special demands wins (less or equal zero <-> arbitrary) + double fx, fy, fz; + fx = fy = fz = -1.0; + for( const std::unique_ptr<VSeriesPlotter>& aPlotter : m_aSeriesPlotterList ) + { + drawing::Direction3D aSingleRatio( aPlotter->getPreferredDiagramAspectRatio() ); + if( fx<0 && aSingleRatio.DirectionX>0 ) + fx = aSingleRatio.DirectionX; + + if( fy<0 && aSingleRatio.DirectionY>0 ) + { + if( fx>0 && aSingleRatio.DirectionX>0 ) + fy = fx*aSingleRatio.DirectionY/aSingleRatio.DirectionX; + else if( fz>0 && aSingleRatio.DirectionZ>0 ) + fy = fz*aSingleRatio.DirectionY/aSingleRatio.DirectionZ; + else + fy = aSingleRatio.DirectionY; + } + + if( fz<0 && aSingleRatio.DirectionZ>0 ) + { + if( fx>0 && aSingleRatio.DirectionX>0 ) + fz = fx*aSingleRatio.DirectionZ/aSingleRatio.DirectionX; + else if( fy>0 && aSingleRatio.DirectionY>0 ) + fz = fy*aSingleRatio.DirectionZ/aSingleRatio.DirectionY; + else + fz = aSingleRatio.DirectionZ; + } + + if( fx>0 && fy>0 && fz>0 ) + break; + } + aPreferredAspectRatio = drawing::Direction3D(fx, fy, fz); + return aPreferredAspectRatio; +} + +} + +struct CreateShapeParam2D +{ + css::awt::Rectangle maRemainingSpace; + + std::shared_ptr<SeriesPlotterContainer> mpSeriesPlotterContainer; + + std::shared_ptr<VTitle> mpVTitleX; + std::shared_ptr<VTitle> mpVTitleY; + std::shared_ptr<VTitle> mpVTitleZ; + + std::shared_ptr<VTitle> mpVTitleSecondX; + std::shared_ptr<VTitle> mpVTitleSecondY; + + css::uno::Reference<css::drawing::XShape> mxMarkHandles; + css::uno::Reference<css::drawing::XShape> mxPlotAreaWithAxes; + + css::uno::Reference<css::drawing::XShapes> mxDiagramWithAxesShapes; + + bool mbAutoPosTitleX; + bool mbAutoPosTitleY; + bool mbAutoPosTitleZ; + + bool mbAutoPosSecondTitleX; + bool mbAutoPosSecondTitleY; + + bool mbUseFixedInnerSize; + + CreateShapeParam2D() : + mbAutoPosTitleX(true), + mbAutoPosTitleY(true), + mbAutoPosTitleZ(true), + mbAutoPosSecondTitleX(true), + mbAutoPosSecondTitleY(true), + mbUseFixedInnerSize(false) {} +}; + +const uno::Sequence<sal_Int8>& ExplicitValueProvider::getUnoTunnelId() +{ + return theExplicitValueProviderUnoTunnelId::get().getSeq(); +} + +ChartView::ChartView( + uno::Reference<uno::XComponentContext> const & xContext, + ChartModel& rModel) + : m_aMutex() + , m_xCC(xContext) + , mrChartModel(rModel) + , m_xShapeFactory() + , m_xDrawPage() + , m_pDrawModelWrapper() + , m_aListenerContainer( m_aMutex ) + , m_bViewDirty(true) + , m_bInViewUpdate(false) + , m_bViewUpdatePending(false) + , m_bRefreshAddIn(true) + , m_aPageResolution(1000,1000) + , m_bPointsWereSkipped(false) + , m_nScaleXNumerator(1) + , m_nScaleXDenominator(1) + , m_nScaleYNumerator(1) + , m_nScaleYDenominator(1) + , m_bSdrViewIsInEditMode(false) + , m_aResultingDiagramRectangleExcludingAxes(0,0,0,0) +{ + init(); +} + +void ChartView::init() +{ + if( !m_pDrawModelWrapper ) + { + SolarMutexGuard aSolarGuard; + m_pDrawModelWrapper = std::make_shared< DrawModelWrapper >(); + m_xShapeFactory = m_pDrawModelWrapper->getShapeFactory(); + m_xDrawPage = m_pDrawModelWrapper->getMainDrawPage(); + StartListening( m_pDrawModelWrapper->getSdrModel() ); + } +} + +void SAL_CALL ChartView::initialize( const uno::Sequence< uno::Any >& ) +{ + init(); +} + +ChartView::~ChartView() +{ + maTimeBased.maTimer.Stop(); + // #i120831#. In ChartView::initialize(), m_xShapeFactory is created from SdrModel::getUnoModel() and indirectly + // from SfxBaseModel, it needs call dispose() to make sure SfxBaseModel object is freed correctly. + uno::Reference< lang::XComponent > xComp( m_xShapeFactory, uno::UNO_QUERY); + if ( xComp.is() ) + xComp->dispose(); + + if( m_pDrawModelWrapper ) + { + SolarMutexGuard aSolarGuard; + EndListening( m_pDrawModelWrapper->getSdrModel() ); + m_pDrawModelWrapper.reset(); + } + m_xDrawPage = nullptr; + impl_deleteCoordinateSystems(); +} + +void ChartView::impl_deleteCoordinateSystems() +{ + //delete all coordinate systems + m_aVCooSysList.clear(); +} + +// datatransfer::XTransferable +namespace +{ +const OUString lcl_aGDIMetaFileMIMEType( + "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"" ); +const OUString lcl_aGDIMetaFileMIMETypeHighContrast( + "application/x-openoffice-highcontrast-gdimetafile;windows_formatname=\"GDIMetaFile\"" ); +} // anonymous namespace + +void ChartView::getMetaFile( const uno::Reference< io::XOutputStream >& xOutStream + , bool bUseHighContrast ) +{ + if( !m_xDrawPage.is() ) + return; + + // creating the graphic exporter + uno::Reference< drawing::XGraphicExportFilter > xExporter = drawing::GraphicExportFilter::create( m_xCC ); + + uno::Sequence< beans::PropertyValue > aProps(3); + aProps[0].Name = "FilterName"; + aProps[0].Value <<= OUString("SVM"); + + aProps[1].Name = "OutputStream"; + aProps[1].Value <<= xOutStream; + + uno::Sequence< beans::PropertyValue > aFilterData(8); + aFilterData[0].Name = "ExportOnlyBackground"; + aFilterData[0].Value <<= false; + aFilterData[1].Name = "HighContrast"; + aFilterData[1].Value <<= bUseHighContrast; + + aFilterData[2].Name = "Version"; + const sal_Int32 nVersion = SOFFICE_FILEFORMAT_50; + aFilterData[2].Value <<= nVersion; + + aFilterData[3].Name = "CurrentPage"; + aFilterData[3].Value <<= uno::Reference< uno::XInterface >( m_xDrawPage, uno::UNO_QUERY ); + + //#i75867# poor quality of ole's alternative view with 3D scenes and zoomfactors besides 100% + aFilterData[4].Name = "ScaleXNumerator"; + aFilterData[4].Value <<= m_nScaleXNumerator; + aFilterData[5].Name = "ScaleXDenominator"; + aFilterData[5].Value <<= m_nScaleXDenominator; + aFilterData[6].Name = "ScaleYNumerator"; + aFilterData[6].Value <<= m_nScaleYNumerator; + aFilterData[7].Name = "ScaleYDenominator"; + aFilterData[7].Value <<= m_nScaleYDenominator; + + + aProps[2].Name = "FilterData"; + aProps[2].Value <<= aFilterData; + + xExporter->setSourceDocument( uno::Reference< lang::XComponent >( m_xDrawPage, uno::UNO_QUERY) ); + if( xExporter->filter( aProps ) ) + { + xOutStream->flush(); + xOutStream->closeOutput(); + uno::Reference< io::XSeekable > xSeekable( xOutStream, uno::UNO_QUERY ); + if( xSeekable.is() ) + xSeekable->seek(0); + } +} + +uno::Any SAL_CALL ChartView::getTransferData( const datatransfer::DataFlavor& aFlavor ) +{ + bool bHighContrastMetaFile( aFlavor.MimeType == lcl_aGDIMetaFileMIMETypeHighContrast); + uno::Any aRet; + if( ! (bHighContrastMetaFile || aFlavor.MimeType == lcl_aGDIMetaFileMIMEType) ) + return aRet; + + update(); + + SvMemoryStream aStream( 1024, 1024 ); + utl::OStreamWrapper* pStreamWrapper = new utl::OStreamWrapper( aStream ); + + uno::Reference< io::XOutputStream > xOutStream( pStreamWrapper ); + uno::Reference< io::XInputStream > xInStream( pStreamWrapper ); + uno::Reference< io::XSeekable > xSeekable( pStreamWrapper ); + + if( xOutStream.is() ) + { + this->getMetaFile( xOutStream, bHighContrastMetaFile ); + + if( xInStream.is() && xSeekable.is() ) + { + xSeekable->seek(0); + sal_Int32 nBytesToRead = xInStream->available(); + uno::Sequence< sal_Int8 > aSeq( nBytesToRead ); + xInStream->readBytes( aSeq, nBytesToRead); + aRet <<= aSeq; + xInStream->closeInput(); + } + } + + return aRet; +} +uno::Sequence< datatransfer::DataFlavor > SAL_CALL ChartView::getTransferDataFlavors() +{ + uno::Sequence< datatransfer::DataFlavor > aRet(2); + + aRet[0] = datatransfer::DataFlavor( lcl_aGDIMetaFileMIMEType, + "GDIMetaFile", + cppu::UnoType<uno::Sequence< sal_Int8 >>::get() ); + aRet[1] = datatransfer::DataFlavor( lcl_aGDIMetaFileMIMETypeHighContrast, + "GDIMetaFile", + cppu::UnoType<uno::Sequence< sal_Int8 >>::get() ); + + return aRet; +} +sal_Bool SAL_CALL ChartView::isDataFlavorSupported( const datatransfer::DataFlavor& aFlavor ) +{ + return ( aFlavor.MimeType == lcl_aGDIMetaFileMIMEType || + aFlavor.MimeType == lcl_aGDIMetaFileMIMETypeHighContrast ); +} + +// ____ XUnoTunnel ___ +::sal_Int64 SAL_CALL ChartView::getSomething( const uno::Sequence< ::sal_Int8 >& aIdentifier ) +{ + if( isUnoTunnelId<ExplicitValueProvider>(aIdentifier) ) + { + ExplicitValueProvider* pProvider = this; + return reinterpret_cast<sal_Int64>(pProvider); + } + return 0; +} + +// lang::XServiceInfo + +OUString SAL_CALL ChartView::getImplementationName() +{ + return CHART_VIEW_SERVICE_IMPLEMENTATION_NAME; +} + +sal_Bool SAL_CALL ChartView::supportsService( const OUString& rServiceName ) +{ + return cppu::supportsService(this, rServiceName); +} + +css::uno::Sequence< OUString > SAL_CALL ChartView::getSupportedServiceNames() +{ + return { CHART_VIEW_SERVICE_NAME }; +} + +static ::basegfx::B3DHomMatrix createTransformationSceneToScreen( + const ::basegfx::B2IRectangle& rDiagramRectangleWithoutAxes ) +{ + ::basegfx::B3DHomMatrix aM; + aM.scale(double(rDiagramRectangleWithoutAxes.getWidth())/FIXED_SIZE_FOR_3D_CHART_VOLUME + , -double(rDiagramRectangleWithoutAxes.getHeight())/FIXED_SIZE_FOR_3D_CHART_VOLUME, 1.0 ); + aM.translate(double(rDiagramRectangleWithoutAxes.getMinX()) + , double(rDiagramRectangleWithoutAxes.getMinY()+rDiagramRectangleWithoutAxes.getHeight()-1), 0); + return aM; +} + +namespace +{ + +bool lcl_IsPieOrDonut( const uno::Reference< XDiagram >& xDiagram ) +{ + //special treatment for pie charts + //the size is checked after complete creation to get the datalabels into the given space + + //todo: this is just a workaround at the moment for pie and donut labels + return DiagramHelper::isPieOrDonutChart( xDiagram ); +} + +void lcl_setDefaultWritingMode( const std::shared_ptr< DrawModelWrapper >& pDrawModelWrapper, ChartModel& rModel) +{ + //get writing mode from parent document: + if( !SvtLanguageOptions().IsCTLFontEnabled() ) + return; + + try + { + sal_Int16 nWritingMode=-1; + uno::Reference< beans::XPropertySet > xParentProps( rModel.getParent(), uno::UNO_QUERY ); + uno::Reference< style::XStyleFamiliesSupplier > xStyleFamiliesSupplier( xParentProps, uno::UNO_QUERY ); + if( xStyleFamiliesSupplier.is() ) + { + uno::Reference< container::XNameAccess > xStylesFamilies( xStyleFamiliesSupplier->getStyleFamilies() ); + if( xStylesFamilies.is() ) + { + if( !xStylesFamilies->hasByName( "PageStyles" ) ) + { + //draw/impress is parent document + uno::Reference< lang::XMultiServiceFactory > xFatcory( xParentProps, uno::UNO_QUERY ); + if( xFatcory.is() ) + { + uno::Reference< beans::XPropertySet > xDrawDefaults( xFatcory->createInstance( "com.sun.star.drawing.Defaults" ), uno::UNO_QUERY ); + if( xDrawDefaults.is() ) + xDrawDefaults->getPropertyValue( "WritingMode" ) >>= nWritingMode; + } + } + else + { + uno::Reference< container::XNameAccess > xPageStyles( xStylesFamilies->getByName( "PageStyles" ), uno::UNO_QUERY ); + if( xPageStyles.is() ) + { + OUString aPageStyle; + + uno::Reference< text::XTextDocument > xTextDocument( xParentProps, uno::UNO_QUERY ); + if( xTextDocument.is() ) + { + //writer is parent document + //retrieve the current page style from the text cursor property PageStyleName + + uno::Reference< text::XTextEmbeddedObjectsSupplier > xTextEmbeddedObjectsSupplier( xTextDocument, uno::UNO_QUERY ); + if( xTextEmbeddedObjectsSupplier.is() ) + { + uno::Reference< container::XNameAccess > xEmbeddedObjects( xTextEmbeddedObjectsSupplier->getEmbeddedObjects() ); + if( xEmbeddedObjects.is() ) + { + uno::Sequence< OUString > aNames( xEmbeddedObjects->getElementNames() ); + + sal_Int32 nCount = aNames.getLength(); + for( sal_Int32 nN=0; nN<nCount; nN++ ) + { + uno::Reference< beans::XPropertySet > xEmbeddedProps( xEmbeddedObjects->getByName( aNames[nN] ), uno::UNO_QUERY ); + if( xEmbeddedProps.is() ) + { + static OUString aChartCLSID = SvGlobalName( SO3_SCH_CLASSID ).GetHexName(); + OUString aCLSID; + xEmbeddedProps->getPropertyValue( "CLSID" ) >>= aCLSID; + if( aCLSID == aChartCLSID ) + { + uno::Reference< text::XTextContent > xEmbeddedObject( xEmbeddedProps, uno::UNO_QUERY ); + if( xEmbeddedObject.is() ) + { + uno::Reference< text::XTextRange > xAnchor( xEmbeddedObject->getAnchor() ); + if( xAnchor.is() ) + { + uno::Reference< beans::XPropertySet > xAnchorProps( xAnchor, uno::UNO_QUERY ); + if( xAnchorProps.is() ) + { + xAnchorProps->getPropertyValue( "WritingMode" ) >>= nWritingMode; + } + uno::Reference< text::XText > xText( xAnchor->getText() ); + if( xText.is() ) + { + uno::Reference< beans::XPropertySet > xTextCursorProps( xText->createTextCursor(), uno::UNO_QUERY ); + if( xTextCursorProps.is() ) + xTextCursorProps->getPropertyValue( "PageStyleName" ) >>= aPageStyle; + } + } + } + break; + } + } + } + } + } + if( aPageStyle.isEmpty() ) + { + uno::Reference< text::XText > xText( xTextDocument->getText() ); + if( xText.is() ) + { + uno::Reference< beans::XPropertySet > xTextCursorProps( xText->createTextCursor(), uno::UNO_QUERY ); + if( xTextCursorProps.is() ) + xTextCursorProps->getPropertyValue( "PageStyleName" ) >>= aPageStyle; + } + } + if(aPageStyle.isEmpty()) + aPageStyle = "Standard"; + } + else + { + //Calc is parent document + Reference< com::sun::star::beans::XPropertySetInfo > xInfo = xParentProps->getPropertySetInfo(); + if (xInfo->hasPropertyByName("PageStyle")) + { + xParentProps->getPropertyValue( "PageStyle" ) >>= aPageStyle; + } + if(aPageStyle.isEmpty()) + aPageStyle = "Default"; + } + if( nWritingMode == -1 || nWritingMode == text::WritingMode2::PAGE ) + { + uno::Reference< beans::XPropertySet > xPageStyle( xPageStyles->getByName( aPageStyle ), uno::UNO_QUERY ); + Reference< com::sun::star::beans::XPropertySetInfo > xInfo = xPageStyle->getPropertySetInfo(); + if (xInfo->hasPropertyByName("WritingMode")) + { + if( xPageStyle.is() ) + xPageStyle->getPropertyValue( "WritingMode" ) >>= nWritingMode; + } + } + } + } + } + } + if( nWritingMode != -1 && nWritingMode != text::WritingMode2::PAGE ) + { + if( pDrawModelWrapper ) + pDrawModelWrapper->GetItemPool().SetPoolDefaultItem(SvxFrameDirectionItem(static_cast<SvxFrameDirection>(nWritingMode), EE_PARA_WRITINGDIR) ); + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + } +} + +sal_Int16 lcl_getDefaultWritingModeFromPool( const std::shared_ptr<DrawModelWrapper>& pDrawModelWrapper ) +{ + sal_Int16 nWritingMode = text::WritingMode2::LR_TB; + if(!pDrawModelWrapper) + return nWritingMode; + + const SfxPoolItem& rItem = pDrawModelWrapper->GetItemPool().GetDefaultItem(EE_PARA_WRITINGDIR); + nWritingMode + = static_cast<sal_Int16>(static_cast<const SvxFrameDirectionItem&>(rItem).GetValue()); + return nWritingMode; +} + +} //end anonymous namespace + +awt::Rectangle ChartView::impl_createDiagramAndContent( const CreateShapeParam2D& rParam, const awt::Size& rPageSize ) +{ + //return the used rectangle + awt::Rectangle aUsedOuterRect(rParam.maRemainingSpace.X, rParam.maRemainingSpace.Y, 0, 0); + + uno::Reference< XDiagram > xDiagram( mrChartModel.getFirstDiagram() ); + if( !xDiagram.is()) + return aUsedOuterRect; + + sal_Int32 nDimensionCount = DiagramHelper::getDimension( xDiagram ); + if(!nDimensionCount) + { + //@todo handle mixed dimension + nDimensionCount = 2; + } + + basegfx::B2IRectangle aAvailableOuterRect = BaseGFXHelper::makeRectangle(rParam.maRemainingSpace); + + const std::vector< std::unique_ptr<VCoordinateSystem> >& rVCooSysList( rParam.mpSeriesPlotterContainer->getCooSysList() ); + SeriesPlottersType& rSeriesPlotterList = rParam.mpSeriesPlotterContainer->getSeriesPlotterList(); + + //create VAxis, so they can give necessary information for automatic scaling + uno::Reference<chart2::XChartDocument> const xChartDoc(&mrChartModel); + uno::Reference<util::XNumberFormatsSupplier> const xNumberFormatsSupplier( + mrChartModel.getNumberFormatsSupplier()); + size_t nC = 0; + for( nC=0; nC < rVCooSysList.size(); nC++) + { + VCoordinateSystem* pVCooSys = rVCooSysList[nC].get(); + if(nDimensionCount==3) + { + uno::Reference<beans::XPropertySet> xSceneProperties( xDiagram, uno::UNO_QUERY ); + CuboidPlanePosition eLeftWallPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardLeftWall( xSceneProperties ) ); + CuboidPlanePosition eBackWallPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardBackWall( xSceneProperties ) ); + CuboidPlanePosition eBottomPos( ThreeDHelper::getAutomaticCuboidPlanePositionForStandardBottom( xSceneProperties ) ); + pVCooSys->set3DWallPositions( eLeftWallPos, eBackWallPos, eBottomPos ); + } + + pVCooSys->createVAxisList(xChartDoc, rPageSize, rParam.maRemainingSpace, rParam.mbUseFixedInnerSize); + } + + // - prepare list of all axis and how they are used + Date aNullDate = NumberFormatterWrapper( xNumberFormatsSupplier ).getNullDate(); + rParam.mpSeriesPlotterContainer->initAxisUsageList(aNullDate); + rParam.mpSeriesPlotterContainer->doAutoScaling( mrChartModel ); + rParam.mpSeriesPlotterContainer->setScalesFromCooSysToPlotter(); + rParam.mpSeriesPlotterContainer->setNumberFormatsFromAxes(); + + //create shapes + + //aspect ratio + drawing::Direction3D aPreferredAspectRatio = + rParam.mpSeriesPlotterContainer->getPreferredAspectRatio(); + + uno::Reference< drawing::XShapes > xSeriesTargetInFrontOfAxis; + uno::Reference< drawing::XShapes > xSeriesTargetBehindAxis; + VDiagram aVDiagram(xDiagram, aPreferredAspectRatio, nDimensionCount); + {//create diagram + aVDiagram.init(rParam.mxDiagramWithAxesShapes, m_xShapeFactory); + aVDiagram.createShapes( + awt::Point(rParam.maRemainingSpace.X, rParam.maRemainingSpace.Y), + awt::Size(rParam.maRemainingSpace.Width, rParam.maRemainingSpace.Height)); + + xSeriesTargetInFrontOfAxis = aVDiagram.getCoordinateRegion(); + // It is preferable to use full size than minimum for pie charts + if (!rParam.mbUseFixedInnerSize) + aVDiagram.reduceToMimimumSize(); + } + + uno::Reference< drawing::XShapes > xTextTargetShapes = + ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory)->createGroup2D(rParam.mxDiagramWithAxesShapes); + + // - create axis and grids for all coordinate systems + + //init all coordinate systems + for( nC=0; nC < rVCooSysList.size(); nC++) + { + VCoordinateSystem* pVCooSys = rVCooSysList[nC].get(); + pVCooSys->initPlottingTargets(xSeriesTargetInFrontOfAxis,xTextTargetShapes,m_xShapeFactory,xSeriesTargetBehindAxis); + + pVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix( + createTransformationSceneToScreen( aVDiagram.getCurrentRectangle() ) )); + + pVCooSys->initVAxisInList(); + } + + //calculate resulting size respecting axis label layout and fontscaling + + uno::Reference< drawing::XShape > xBoundingShape(rParam.mxDiagramWithAxesShapes, uno::UNO_QUERY); + ::basegfx::B2IRectangle aConsumedOuterRect; + + //use first coosys only so far; todo: calculate for more than one coosys if we have more in future + //todo: this is just a workaround at the moment for pie and donut labels + bool bIsPieOrDonut = lcl_IsPieOrDonut(xDiagram); + if( !bIsPieOrDonut && (!rVCooSysList.empty()) ) + { + VCoordinateSystem* pVCooSys = rVCooSysList[0].get(); + pVCooSys->createMaximumAxesLabels(); + + aConsumedOuterRect = ShapeFactory::getRectangleOfShape(xBoundingShape); + ::basegfx::B2IRectangle aNewInnerRect( aVDiagram.getCurrentRectangle() ); + if (!rParam.mbUseFixedInnerSize) + aNewInnerRect = aVDiagram.adjustInnerSize( aConsumedOuterRect ); + + pVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix( + createTransformationSceneToScreen( aNewInnerRect ) )); + + //redo autoscaling to get size and text dependent automatic main increment count + rParam.mpSeriesPlotterContainer->doAutoScaling( mrChartModel ); + rParam.mpSeriesPlotterContainer->updateScalesAndIncrementsOnAxes(); + rParam.mpSeriesPlotterContainer->setScalesFromCooSysToPlotter(); + + pVCooSys->createAxesLabels(); + + bool bLessSpaceConsumedThanExpected = false; + { + aConsumedOuterRect = ShapeFactory::getRectangleOfShape(xBoundingShape); + if( aConsumedOuterRect.getMinX() > aAvailableOuterRect.getMinX() + || aConsumedOuterRect.getMaxX() < aAvailableOuterRect.getMaxX() + || aConsumedOuterRect.getMinY() > aAvailableOuterRect.getMinY() + || aConsumedOuterRect.getMinY() < aAvailableOuterRect.getMaxY() ) + bLessSpaceConsumedThanExpected = true; + } + + if (bLessSpaceConsumedThanExpected && !rParam.mbUseFixedInnerSize) + { + aVDiagram.adjustInnerSize( aConsumedOuterRect ); + pVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix( + createTransformationSceneToScreen( aVDiagram.getCurrentRectangle() ) )); + } + pVCooSys->updatePositions();//todo: logically this belongs to the condition above, but it seems also to be necessary to give the axes group shapes the right bounding rects for hit test - probably caused by bug i106183 -> check again if fixed + } + + //create axes and grids for the final size + for( nC=0; nC < rVCooSysList.size(); nC++) + { + VCoordinateSystem* pVCooSys = rVCooSysList[nC].get(); + + pVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix( + createTransformationSceneToScreen( aVDiagram.getCurrentRectangle() ) )); + + pVCooSys->createAxesShapes(); + pVCooSys->createGridShapes(); + } + + // - create data series for all charttypes + m_bPointsWereSkipped = false; + for( const std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList ) + { + VSeriesPlotter* pSeriesPlotter = aPlotter.get(); + uno::Reference< drawing::XShapes > xSeriesTarget; + if( pSeriesPlotter->WantToPlotInFrontOfAxisLine() ) + xSeriesTarget = xSeriesTargetInFrontOfAxis; + else + { + xSeriesTarget = xSeriesTargetBehindAxis; + OSL_ENSURE( !bIsPieOrDonut, "not implemented yet! - during a complete recreation this shape is destroyed so no series can be created anymore" ); + } + pSeriesPlotter->initPlotter( xSeriesTarget,xTextTargetShapes,m_xShapeFactory,OUString() ); + pSeriesPlotter->setPageReferenceSize( rPageSize ); + VCoordinateSystem* pVCooSys = lcl_getCooSysForPlotter( rVCooSysList, pSeriesPlotter ); + if(nDimensionCount==2) + pSeriesPlotter->setTransformationSceneToScreen( pVCooSys->getTransformationSceneToScreen() ); + //better performance for big data + { + //calculate resolution for coordinate system + Sequence<sal_Int32> aCoordinateSystemResolution = pVCooSys->getCoordinateSystemResolution( rPageSize, m_aPageResolution ); + pSeriesPlotter->setCoordinateSystemResolution( aCoordinateSystemResolution ); + } + // Do not allow to move data labels in case of pie or donut chart, yet! + pSeriesPlotter->setPieLabelsAllowToMove(!bIsPieOrDonut); + pSeriesPlotter->createShapes(); + m_bPointsWereSkipped = m_bPointsWereSkipped || pSeriesPlotter->PointsWereSkipped(); + } + + //recreate all with corrected sizes if requested + if( bIsPieOrDonut ) + { + m_bPointsWereSkipped = false; + + aConsumedOuterRect = ShapeFactory::getRectangleOfShape(xBoundingShape); + ::basegfx::B2IRectangle aNewInnerRect( aVDiagram.getCurrentRectangle() ); + if (!rParam.mbUseFixedInnerSize) + aNewInnerRect = aVDiagram.adjustInnerSize( aConsumedOuterRect ); + + for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList ) + { + aPlotter->releaseShapes(); + } + + //clear and recreate + ShapeFactory::removeSubShapes( xSeriesTargetInFrontOfAxis ); //xSeriesTargetBehindAxis is a sub shape of xSeriesTargetInFrontOfAxis and will be removed here + xSeriesTargetBehindAxis.clear(); + ShapeFactory::removeSubShapes( xTextTargetShapes ); + + //set new transformation + for( nC=0; nC < rVCooSysList.size(); nC++) + { + VCoordinateSystem* pVCooSys = rVCooSysList[nC].get(); + pVCooSys->setTransformationSceneToScreen( B3DHomMatrixToHomogenMatrix( + createTransformationSceneToScreen( aNewInnerRect ) )); + } + + // - create data series for all charttypes + for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList ) + { + VCoordinateSystem* pVCooSys = lcl_getCooSysForPlotter( rVCooSysList, aPlotter.get() ); + if(nDimensionCount==2) + aPlotter->setTransformationSceneToScreen( pVCooSys->getTransformationSceneToScreen() ); + // Now we can move data labels in case of pie or donut chart! + aPlotter->setPieLabelsAllowToMove(bIsPieOrDonut); + aPlotter->createShapes(); + m_bPointsWereSkipped = m_bPointsWereSkipped || aPlotter->PointsWereSkipped(); + } + + for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList ) + aPlotter->rearrangeLabelToAvoidOverlapIfRequested(rPageSize); + } + + if (rParam.mbUseFixedInnerSize) + { + aUsedOuterRect = awt::Rectangle( aConsumedOuterRect.getMinX(), aConsumedOuterRect.getMinY(), aConsumedOuterRect.getWidth(), aConsumedOuterRect.getHeight() ); + } + else + aUsedOuterRect = rParam.maRemainingSpace; + + bool bSnapRectToUsedArea = false; + for( std::unique_ptr<VSeriesPlotter>& aPlotter : rSeriesPlotterList ) + { + bSnapRectToUsedArea = aPlotter->shouldSnapRectToUsedArea(); + if(bSnapRectToUsedArea) + break; + } + if(bSnapRectToUsedArea) + { + if (rParam.mbUseFixedInnerSize) + m_aResultingDiagramRectangleExcludingAxes = getRectangleOfObject( "PlotAreaExcludingAxes" ); + else + { + ::basegfx::B2IRectangle aConsumedInnerRect = aVDiagram.getCurrentRectangle(); + m_aResultingDiagramRectangleExcludingAxes = awt::Rectangle( aConsumedInnerRect.getMinX(), aConsumedInnerRect.getMinY(), aConsumedInnerRect.getWidth(), aConsumedInnerRect.getHeight() ); + } + } + else + { + if (rParam.mbUseFixedInnerSize) + m_aResultingDiagramRectangleExcludingAxes = rParam.maRemainingSpace; + else + { + ::basegfx::B2IRectangle aConsumedInnerRect = aVDiagram.getCurrentRectangle(); + m_aResultingDiagramRectangleExcludingAxes = awt::Rectangle( aConsumedInnerRect.getMinX(), aConsumedInnerRect.getMinY(), aConsumedInnerRect.getWidth(), aConsumedInnerRect.getHeight() ); + } + } + + if (rParam.mxMarkHandles.is()) + { + awt::Point aPos(rParam.maRemainingSpace.X, rParam.maRemainingSpace.Y); + awt::Size aSize(rParam.maRemainingSpace.Width, rParam.maRemainingSpace.Height); + + bool bPosSizeExcludeAxesProperty = true; + uno::Reference< beans::XPropertySet > xDiaProps( xDiagram, uno::UNO_QUERY_THROW ); + xDiaProps->getPropertyValue("PosSizeExcludeAxes") >>= bPosSizeExcludeAxesProperty; + if (rParam.mbUseFixedInnerSize || bPosSizeExcludeAxesProperty) + { + aPos = awt::Point( m_aResultingDiagramRectangleExcludingAxes.X, m_aResultingDiagramRectangleExcludingAxes.Y ); + aSize = awt::Size( m_aResultingDiagramRectangleExcludingAxes.Width, m_aResultingDiagramRectangleExcludingAxes.Height ); + } + rParam.mxMarkHandles->setPosition(aPos); + rParam.mxMarkHandles->setSize(aSize); + } + + return aUsedOuterRect; +} + +bool ChartView::getExplicitValuesForAxis( + uno::Reference< XAxis > xAxis + , ExplicitScaleData& rExplicitScale + , ExplicitIncrementData& rExplicitIncrement ) +{ + SolarMutexGuard aSolarGuard; + + impl_updateView(); + + if(!xAxis.is()) + return false; + + uno::Reference< XCoordinateSystem > xCooSys( AxisHelper::getCoordinateSystemOfAxis(xAxis, mrChartModel.getFirstDiagram() ) ); + const VCoordinateSystem* pVCooSys = findInCooSysList(m_aVCooSysList,xCooSys); + if(!pVCooSys) + return false; + + sal_Int32 nDimensionIndex=-1; + sal_Int32 nAxisIndex=-1; + if( AxisHelper::getIndicesForAxis( xAxis, xCooSys, nDimensionIndex, nAxisIndex ) ) + { + rExplicitScale = pVCooSys->getExplicitScale(nDimensionIndex,nAxisIndex); + rExplicitIncrement = pVCooSys->getExplicitIncrement(nDimensionIndex,nAxisIndex); + if( rExplicitScale.ShiftedCategoryPosition ) + { + //remove 'one' from max + if( rExplicitScale.AxisType == css::chart2::AxisType::DATE ) + { + Date aMaxDate(rExplicitScale.NullDate); aMaxDate.AddDays(::rtl::math::approxFloor(rExplicitScale.Maximum)); + //for explicit scales with shifted categories we need one interval more + switch( rExplicitScale.TimeResolution ) + { + case css::chart::TimeUnit::DAY: + --aMaxDate; + break; + case css::chart::TimeUnit::MONTH: + aMaxDate = DateHelper::GetDateSomeMonthsAway(aMaxDate,-1); + break; + case css::chart::TimeUnit::YEAR: + aMaxDate = DateHelper::GetDateSomeYearsAway(aMaxDate,-1); + break; + } + rExplicitScale.Maximum = aMaxDate - rExplicitScale.NullDate; + } + else if( rExplicitScale.AxisType == css::chart2::AxisType::CATEGORY ) + rExplicitScale.Maximum -= 1.0; + else if( rExplicitScale.AxisType == css::chart2::AxisType::SERIES ) + rExplicitScale.Maximum -= 1.0; + } + return true; + } + return false; +} + +SdrPage* ChartView::getSdrPage() +{ + auto pSvxDrawPage = comphelper::getUnoTunnelImplementation<SvxDrawPage>(m_xDrawPage); + if(pSvxDrawPage) + return pSvxDrawPage->GetSdrPage(); + + return nullptr; +} + +uno::Reference< drawing::XShape > ChartView::getShapeForCID( const OUString& rObjectCID ) +{ + SolarMutexGuard aSolarGuard; + SdrObject* pObj = DrawModelWrapper::getNamedSdrObject( rObjectCID, this->getSdrPage() ); + if( pObj ) + return uno::Reference< drawing::XShape >( pObj->getUnoShape(), uno::UNO_QUERY); + return nullptr; +} + +awt::Rectangle ChartView::getDiagramRectangleExcludingAxes() +{ + impl_updateView(); + return m_aResultingDiagramRectangleExcludingAxes; +} + +awt::Rectangle ChartView::getRectangleOfObject( const OUString& rObjectCID, bool bSnapRect ) +{ + impl_updateView(); + + awt::Rectangle aRet; + uno::Reference< drawing::XShape > xShape( getShapeForCID(rObjectCID) ); + if(xShape.is()) + { + //special handling for axis for old api: + //same special handling for diagram + ObjectType eObjectType( ObjectIdentifier::getObjectType( rObjectCID ) ); + if( eObjectType == OBJECTTYPE_AXIS || eObjectType == OBJECTTYPE_DIAGRAM ) + { + SolarMutexGuard aSolarGuard; + SvxShape* pRoot = comphelper::getUnoTunnelImplementation<SvxShape>( xShape ); + if( pRoot ) + { + SdrObject* pRootSdrObject = pRoot->GetSdrObject(); + if( pRootSdrObject ) + { + SdrObjList* pRootList = pRootSdrObject->GetSubList(); + if( pRootList ) + { + OUString aShapeName = "MarkHandles"; + if( eObjectType == OBJECTTYPE_DIAGRAM ) + aShapeName = "PlotAreaIncludingAxes"; + SdrObject* pShape = DrawModelWrapper::getNamedSdrObject( aShapeName, pRootList ); + if( pShape ) + xShape.set( pShape->getUnoShape(), uno::UNO_QUERY); + } + } + } + } + + awt::Size aSize( xShape->getSize() ); + awt::Point aPoint( xShape->getPosition() ); + aRet = awt::Rectangle( aPoint.X, aPoint.Y, aSize.Width, aSize.Height ); + if( bSnapRect ) + { + //for rotated objects the shape size and position differs from the visible rectangle + SvxShape* pShape = comphelper::getUnoTunnelImplementation<SvxShape>( xShape ); + if( pShape ) + { + SdrObject* pSdrObject = pShape->GetSdrObject(); + if( pSdrObject ) + { + tools::Rectangle aSnapRect( pSdrObject->GetSnapRect() ); + aRet = awt::Rectangle(aSnapRect.Left(),aSnapRect.Top(),aSnapRect.GetWidth(),aSnapRect.GetHeight()); + } + } + } + } + return aRet; +} + +std::shared_ptr< DrawModelWrapper > ChartView::getDrawModelWrapper() +{ + return m_pDrawModelWrapper; +} + +namespace +{ +sal_Int32 lcl_getDiagramTitleSpace() +{ + return 200; //=0,2 cm spacing +} +bool lcl_getPropertySwapXAndYAxis( const uno::Reference< XDiagram >& xDiagram ) +{ + bool bSwapXAndY = false; + + uno::Reference< XCoordinateSystemContainer > xCooSysContainer( xDiagram, uno::UNO_QUERY ); + if( xCooSysContainer.is() ) + { + uno::Sequence< uno::Reference< XCoordinateSystem > > aCooSysList( xCooSysContainer->getCoordinateSystems() ); + if( aCooSysList.hasElements() ) + { + uno::Reference<beans::XPropertySet> xProp(aCooSysList[0], uno::UNO_QUERY ); + if( xProp.is()) try + { + xProp->getPropertyValue( "SwapXAndYAxis" ) >>= bSwapXAndY; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } + return bSwapXAndY; +} + +} + +sal_Int32 ExplicitValueProvider::getExplicitNumberFormatKeyForAxis( + const Reference< chart2::XAxis >& xAxis + , const Reference< chart2::XCoordinateSystem > & xCorrespondingCoordinateSystem + , const Reference<chart2::XChartDocument>& xChartDoc) +{ + return AxisHelper::getExplicitNumberFormatKeyForAxis( xAxis, xCorrespondingCoordinateSystem, xChartDoc + , true /*bSearchForParallelAxisIfNothingIsFound*/ ); +} + +sal_Int32 ExplicitValueProvider::getExplicitNumberFormatKeyForDataLabel( const uno::Reference< beans::XPropertySet >& xSeriesOrPointProp ) +{ + sal_Int32 nFormat=0; + if( !xSeriesOrPointProp.is() ) + return nFormat; + + try + { + xSeriesOrPointProp->getPropertyValue(CHART_UNONAME_NUMFMT) >>= nFormat; + } + catch (const beans::UnknownPropertyException&) {} + + if(nFormat<0) + nFormat=0; + return nFormat; +} + +sal_Int32 ExplicitValueProvider::getExplicitPercentageNumberFormatKeyForDataLabel( + const uno::Reference< beans::XPropertySet >& xSeriesOrPointProp, + const uno::Reference< util::XNumberFormatsSupplier >& xNumberFormatsSupplier ) +{ + sal_Int32 nFormat=0; + if( !xSeriesOrPointProp.is() ) + return nFormat; + if( !(xSeriesOrPointProp->getPropertyValue("PercentageNumberFormat") >>= nFormat) ) + { + nFormat = DiagramHelper::getPercentNumberFormat( xNumberFormatsSupplier ); + } + if(nFormat<0) + nFormat=0; + return nFormat; +} + +awt::Rectangle ExplicitValueProvider::AddSubtractAxisTitleSizes( + ChartModel& rModel + , const Reference< uno::XInterface >& xChartView + , const awt::Rectangle& rPositionAndSize, bool bSubtract ) +{ + awt::Rectangle aRet(rPositionAndSize); + + //add axis title sizes to the diagram size + uno::Reference< chart2::XTitle > xTitle_Height( TitleHelper::getTitle( TitleHelper::TITLE_AT_STANDARD_X_AXIS_POSITION, rModel ) ); + uno::Reference< chart2::XTitle > xTitle_Width( TitleHelper::getTitle( TitleHelper::TITLE_AT_STANDARD_Y_AXIS_POSITION, rModel ) ); + uno::Reference< chart2::XTitle > xSecondTitle_Height( TitleHelper::getTitle( TitleHelper::SECONDARY_X_AXIS_TITLE, rModel ) ); + uno::Reference< chart2::XTitle > xSecondTitle_Width( TitleHelper::getTitle( TitleHelper::SECONDARY_Y_AXIS_TITLE, rModel ) ); + if( xTitle_Height.is() || xTitle_Width.is() || xSecondTitle_Height.is() || xSecondTitle_Width.is() ) + { + ExplicitValueProvider* pExplicitValueProvider = comphelper::getUnoTunnelImplementation<ExplicitValueProvider>(xChartView); + if( pExplicitValueProvider ) + { + //detect whether x axis points into x direction or not + if( lcl_getPropertySwapXAndYAxis( rModel.getFirstDiagram() ) ) + { + std::swap( xTitle_Height, xTitle_Width ); + std::swap( xSecondTitle_Height, xSecondTitle_Width ); + } + + sal_Int32 nTitleSpaceWidth = 0; + sal_Int32 nTitleSpaceHeight = 0; + sal_Int32 nSecondTitleSpaceWidth = 0; + sal_Int32 nSecondTitleSpaceHeight = 0; + + if( xTitle_Height.is() ) + { + OUString aCID_X( ObjectIdentifier::createClassifiedIdentifierForObject( xTitle_Height, rModel ) ); + nTitleSpaceHeight = pExplicitValueProvider->getRectangleOfObject( aCID_X, true ).Height; + if( nTitleSpaceHeight ) + nTitleSpaceHeight+=lcl_getDiagramTitleSpace(); + } + if( xTitle_Width.is() ) + { + OUString aCID_Y( ObjectIdentifier::createClassifiedIdentifierForObject( xTitle_Width, rModel ) ); + nTitleSpaceWidth = pExplicitValueProvider->getRectangleOfObject( aCID_Y, true ).Width; + if(nTitleSpaceWidth) + nTitleSpaceWidth+=lcl_getDiagramTitleSpace(); + } + if( xSecondTitle_Height.is() ) + { + OUString aCID_X( ObjectIdentifier::createClassifiedIdentifierForObject( xSecondTitle_Height, rModel ) ); + nSecondTitleSpaceHeight = pExplicitValueProvider->getRectangleOfObject( aCID_X, true ).Height; + if( nSecondTitleSpaceHeight ) + nSecondTitleSpaceHeight+=lcl_getDiagramTitleSpace(); + } + if( xSecondTitle_Width.is() ) + { + OUString aCID_Y( ObjectIdentifier::createClassifiedIdentifierForObject( xSecondTitle_Width, rModel ) ); + nSecondTitleSpaceWidth += pExplicitValueProvider->getRectangleOfObject( aCID_Y, true ).Width; + if( nSecondTitleSpaceWidth ) + nSecondTitleSpaceWidth+=lcl_getDiagramTitleSpace(); + } + if( bSubtract ) + { + aRet.X += nTitleSpaceWidth; + aRet.Y += nSecondTitleSpaceHeight; + aRet.Width -= (nTitleSpaceWidth + nSecondTitleSpaceWidth); + aRet.Height -= (nTitleSpaceHeight + nSecondTitleSpaceHeight); + } + else + { + + aRet.X -= nTitleSpaceWidth; + aRet.Y -= nSecondTitleSpaceHeight; + aRet.Width += nTitleSpaceWidth + nSecondTitleSpaceWidth; + aRet.Height += nTitleSpaceHeight + nSecondTitleSpaceHeight; + } + } + } + return aRet; +} + +namespace { + +double lcl_getPageLayoutDistancePercentage() +{ + return 0.02; +} + +bool getAvailablePosAndSizeForDiagram( + CreateShapeParam2D& rParam, const awt::Size & rPageSize, const uno::Reference< beans::XPropertySet >& xProp) +{ + rParam.mbUseFixedInnerSize = false; + + //@todo: we need a size dependent on the axis labels + sal_Int32 nYDistance = static_cast<sal_Int32>(rPageSize.Height*lcl_getPageLayoutDistancePercentage()); + sal_Int32 nXDistance = static_cast<sal_Int32>(rPageSize.Width*lcl_getPageLayoutDistancePercentage()); + rParam.maRemainingSpace.X += nXDistance; + rParam.maRemainingSpace.Width -= 2*nXDistance; + rParam.maRemainingSpace.Y += nYDistance; + rParam.maRemainingSpace.Height -= 2*nYDistance; + + bool bPosSizeExcludeAxes = false; + if( xProp.is() ) + xProp->getPropertyValue( "PosSizeExcludeAxes" ) >>= bPosSizeExcludeAxes; + + //size: + css::chart2::RelativeSize aRelativeSize; + if( xProp.is() && (xProp->getPropertyValue( "RelativeSize" )>>=aRelativeSize) ) + { + rParam.maRemainingSpace.Height = static_cast<sal_Int32>(aRelativeSize.Secondary*rPageSize.Height); + rParam.maRemainingSpace.Width = static_cast<sal_Int32>(aRelativeSize.Primary*rPageSize.Width); + rParam.mbUseFixedInnerSize = bPosSizeExcludeAxes; + } + + if (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0) + return false; + + //position: + chart2::RelativePosition aRelativePosition; + if( xProp.is() && (xProp->getPropertyValue( "RelativePosition" )>>=aRelativePosition) ) + { + //@todo decide whether x is primary or secondary + + //the coordinates re relative to the page + double fX = aRelativePosition.Primary*rPageSize.Width; + double fY = aRelativePosition.Secondary*rPageSize.Height; + + awt::Point aPos = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject( + awt::Point(static_cast<sal_Int32>(fX),static_cast<sal_Int32>(fY)), + awt::Size(rParam.maRemainingSpace.Width, rParam.maRemainingSpace.Height), + aRelativePosition.Anchor); + + rParam.maRemainingSpace.X = aPos.X; + rParam.maRemainingSpace.Y = aPos.Y; + + rParam.mbUseFixedInnerSize = bPosSizeExcludeAxes; + } + + //ensure that the diagram does not lap out right side or out of bottom + if (rParam.maRemainingSpace.Y + rParam.maRemainingSpace.Height > rPageSize.Height) + rParam.maRemainingSpace.Height = rPageSize.Height - rParam.maRemainingSpace.Y; + + if (rParam.maRemainingSpace.X + rParam.maRemainingSpace.Width > rPageSize.Width) + rParam.maRemainingSpace.Width = rPageSize.Width - rParam.maRemainingSpace.X; + + return true; +} + +enum TitleAlignment { ALIGN_LEFT, ALIGN_TOP, ALIGN_RIGHT, ALIGN_BOTTOM, ALIGN_Z }; + +void changePositionOfAxisTitle( VTitle* pVTitle, TitleAlignment eAlignment + , awt::Rectangle const & rDiagramPlusAxesRect, const awt::Size & rPageSize ) +{ + if(!pVTitle) + return; + + awt::Point aNewPosition(0,0); + awt::Size aTitleSize = pVTitle->getFinalSize(); + sal_Int32 nYDistance = static_cast<sal_Int32>(rPageSize.Height*lcl_getPageLayoutDistancePercentage()); + sal_Int32 nXDistance = static_cast<sal_Int32>(rPageSize.Width*lcl_getPageLayoutDistancePercentage()); + switch( eAlignment ) + { + case ALIGN_TOP: + aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width/2 + , rDiagramPlusAxesRect.Y - aTitleSize.Height/2 - nYDistance ); + break; + case ALIGN_BOTTOM: + aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width/2 + , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height + aTitleSize.Height/2 + nYDistance ); + break; + case ALIGN_LEFT: + aNewPosition = awt::Point( rDiagramPlusAxesRect.X - aTitleSize.Width/2 - nXDistance + , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height/2 ); + break; + case ALIGN_RIGHT: + aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width + aTitleSize.Width/2 + nXDistance + , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height/2 ); + break; + case ALIGN_Z: + aNewPosition = awt::Point( rDiagramPlusAxesRect.X + rDiagramPlusAxesRect.Width + aTitleSize.Width/2 + nXDistance + , rDiagramPlusAxesRect.Y + rDiagramPlusAxesRect.Height - aTitleSize.Height/2 ); + break; + default: + break; + } + + sal_Int32 nMaxY = rPageSize.Height - aTitleSize.Height/2; + sal_Int32 nMaxX = rPageSize.Width - aTitleSize.Width/2; + sal_Int32 nMinX = aTitleSize.Width/2; + sal_Int32 nMinY = aTitleSize.Height/2; + if( aNewPosition.Y > nMaxY ) + aNewPosition.Y = nMaxY; + if( aNewPosition.X > nMaxX ) + aNewPosition.X = nMaxX; + if( aNewPosition.Y < nMinY ) + aNewPosition.Y = nMinY; + if( aNewPosition.X < nMinX ) + aNewPosition.X = nMinX; + + pVTitle->changePosition( aNewPosition ); +} + +std::shared_ptr<VTitle> lcl_createTitle( TitleHelper::eTitleType eType + , const uno::Reference< drawing::XShapes>& xPageShapes + , const uno::Reference< lang::XMultiServiceFactory>& xShapeFactory + , ChartModel& rModel + , awt::Rectangle& rRemainingSpace + , const awt::Size & rPageSize + , TitleAlignment eAlignment + , bool& rbAutoPosition ) +{ + std::shared_ptr<VTitle> apVTitle; + + // #i109336# Improve auto positioning in chart + double fPercentage = lcl_getPageLayoutDistancePercentage(); + sal_Int32 nXDistance = static_cast< sal_Int32 >( rPageSize.Width * fPercentage ); + sal_Int32 nYDistance = static_cast< sal_Int32 >( rPageSize.Height * fPercentage ); + if ( eType == TitleHelper::MAIN_TITLE ) + { + nYDistance += 135; // 1/100 mm + } + else if ( eType == TitleHelper::TITLE_AT_STANDARD_X_AXIS_POSITION ) + { + nYDistance = 420; // 1/100 mm + } + else if ( eType == TitleHelper::TITLE_AT_STANDARD_Y_AXIS_POSITION ) + { + nXDistance = 450; // 1/100 mm + } + + uno::Reference< XTitle > xTitle( TitleHelper::getTitle( eType, rModel ) ); + OUString aCompleteString = TitleHelper::getCompleteString(xTitle); + if (aCompleteString.isEmpty()) + return apVTitle; + + //create title + awt::Size aTextMaxWidth(rPageSize.Width, rPageSize.Height); + if (eType == TitleHelper::MAIN_TITLE || eType == TitleHelper::SUB_TITLE) + { + aTextMaxWidth.Width = static_cast<sal_Int32>(rPageSize.Width * 0.8); + aTextMaxWidth.Height = static_cast<sal_Int32>(rPageSize.Height * 0.5); + } + apVTitle = std::make_shared<VTitle>(xTitle); + OUString aCID = ObjectIdentifier::createClassifiedIdentifierForObject(xTitle, rModel); + apVTitle->init(xPageShapes, xShapeFactory, aCID); + apVTitle->createShapes(awt::Point(0, 0), rPageSize, aTextMaxWidth); + awt::Size aTitleUnrotatedSize = apVTitle->getUnrotatedSize(); + awt::Size aTitleSize = apVTitle->getFinalSize(); + + //position + rbAutoPosition = true; + awt::Point aNewPosition(0,0); + chart2::RelativePosition aRelativePosition; + uno::Reference<beans::XPropertySet> xProp(xTitle, uno::UNO_QUERY); + if (xProp.is() && (xProp->getPropertyValue("RelativePosition") >>= aRelativePosition)) + { + rbAutoPosition = false; + + //@todo decide whether x is primary or secondary + double fX = aRelativePosition.Primary*rPageSize.Width; + double fY = aRelativePosition.Secondary*rPageSize.Height; + + double fAnglePi = apVTitle->getRotationAnglePi(); + aNewPosition = RelativePositionHelper::getCenterOfAnchoredObject( + awt::Point(static_cast<sal_Int32>(fX),static_cast<sal_Int32>(fY)) + , aTitleUnrotatedSize, aRelativePosition.Anchor, fAnglePi ); + } + else //auto position + { + switch( eAlignment ) + { + case ALIGN_TOP: + aNewPosition = awt::Point( rRemainingSpace.X + rRemainingSpace.Width/2 + , rRemainingSpace.Y + aTitleSize.Height/2 + nYDistance ); + break; + case ALIGN_BOTTOM: + aNewPosition = awt::Point( rRemainingSpace.X + rRemainingSpace.Width/2 + , rRemainingSpace.Y + rRemainingSpace.Height - aTitleSize.Height/2 - nYDistance ); + break; + case ALIGN_LEFT: + aNewPosition = awt::Point( rRemainingSpace.X + aTitleSize.Width/2 + nXDistance + , rRemainingSpace.Y + rRemainingSpace.Height/2 ); + break; + case ALIGN_RIGHT: + aNewPosition = awt::Point( rRemainingSpace.X + rRemainingSpace.Width - aTitleSize.Width/2 - nXDistance + , rRemainingSpace.Y + rRemainingSpace.Height/2 ); + break; + default: + break; + + } + } + apVTitle->changePosition( aNewPosition ); + + //remaining space + switch( eAlignment ) + { + case ALIGN_TOP: + // Push the remaining space down from top. + rRemainingSpace.Y += ( aTitleSize.Height + nYDistance ); + rRemainingSpace.Height -= ( aTitleSize.Height + nYDistance ); + break; + case ALIGN_BOTTOM: + // Push the remaining space up from bottom. + rRemainingSpace.Height -= ( aTitleSize.Height + nYDistance ); + break; + case ALIGN_LEFT: + // Push the remaining space to the right from left edge. + rRemainingSpace.X += ( aTitleSize.Width + nXDistance ); + rRemainingSpace.Width -= ( aTitleSize.Width + nXDistance ); + break; + case ALIGN_RIGHT: + // Push the remaining space to the left from right edge. + rRemainingSpace.Width -= ( aTitleSize.Width + nXDistance ); + break; + default: + break; + } + + return apVTitle; +} + +bool lcl_createLegend( const uno::Reference< XLegend > & xLegend + , const uno::Reference< drawing::XShapes>& xPageShapes + , const uno::Reference< lang::XMultiServiceFactory>& xShapeFactory + , const uno::Reference< uno::XComponentContext > & xContext + , awt::Rectangle & rRemainingSpace + , const awt::Size & rPageSize + , ChartModel& rModel + , const std::vector< LegendEntryProvider* >& rLegendEntryProviderList + , sal_Int16 nDefaultWritingMode ) +{ + if (!VLegend::isVisible(xLegend)) + return false; + + awt::Size rDefaultLegendSize; + VLegend aVLegend( xLegend, xContext, rLegendEntryProviderList, + xPageShapes, xShapeFactory, rModel); + aVLegend.setDefaultWritingMode( nDefaultWritingMode ); + aVLegend.createShapes( awt::Size( rRemainingSpace.Width, rRemainingSpace.Height ), + rPageSize, rDefaultLegendSize ); + aVLegend.changePosition( rRemainingSpace, rPageSize, rDefaultLegendSize ); + return true; +} + +void lcl_createButtons(const uno::Reference<drawing::XShapes>& xPageShapes, + const uno::Reference<lang::XMultiServiceFactory>& xShapeFactory, + ChartModel& rModel, + awt::Rectangle& rRemainingSpace) +{ + uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(rModel.getDataProvider(), uno::UNO_QUERY); + if (!xPivotTableDataProvider.is()) + return; + + uno::Reference<beans::XPropertySet> xModelPage(rModel.getPageBackground()); + + awt::Size aSize(4000, 700); // size of the button + + long x = 0; + + if (xPivotTableDataProvider->getPageFields().hasElements()) + { + x = 0; + + const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getPageFields(); + for (css::chart2::data::PivotTableFieldEntry const & rPageFieldEntry : aPivotFieldEntries) + { + std::unique_ptr<VButton> pButton(new VButton); + pButton->init(xPageShapes, xShapeFactory); + awt::Point aNewPosition(rRemainingSpace.X + x + 100, rRemainingSpace.Y + 100); + sal_Int32 nDimensionIndex = rPageFieldEntry.DimensionIndex; + OUString aFieldOutputDescription = xPivotTableDataProvider->getFieldOutputDescription(nDimensionIndex); + pButton->setLabel(rPageFieldEntry.Name + " | " + aFieldOutputDescription); + pButton->setCID("FieldButton.Page." + OUString::number(nDimensionIndex)); + pButton->setPosition(aNewPosition); + pButton->setSize(aSize); + if (rPageFieldEntry.HasHiddenMembers) + pButton->setArrowColor(Color(0x0000FF)); + + pButton->createShapes(xModelPage); + x += aSize.Width + 100; + } + rRemainingSpace.Y += (aSize.Height + 100 + 100); + rRemainingSpace.Height -= (aSize.Height + 100 + 100); + } + + aSize = awt::Size(3000, 700); // size of the button + + if (!xPivotTableDataProvider->getRowFields().hasElements()) + return; + + x = 200; + const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getRowFields(); + for (css::chart2::data::PivotTableFieldEntry const & rRowFieldEntry : aPivotFieldEntries) + { + + std::unique_ptr<VButton> pButton(new VButton); + pButton->init(xPageShapes, xShapeFactory); + awt::Point aNewPosition(rRemainingSpace.X + x + 100, + rRemainingSpace.Y + rRemainingSpace.Height - aSize.Height - 100); + pButton->setLabel(rRowFieldEntry.Name); + pButton->setCID("FieldButton.Row." + OUString::number(rRowFieldEntry.DimensionIndex)); + pButton->setPosition(aNewPosition); + pButton->setSize(aSize); + if ( rRowFieldEntry.Name == "Data" ) + { + pButton->setBGColor( Color(0x00F6F6F6) ); + pButton->showArrow( false ); + } + else if (rRowFieldEntry.HasHiddenMembers) + pButton->setArrowColor(Color(0x0000FF)); + pButton->createShapes(xModelPage); + x += aSize.Width + 100; + } + rRemainingSpace.Height -= (aSize.Height + 100 + 100); +} + +void formatPage( + ChartModel& rChartModel + , const awt::Size& rPageSize + , const uno::Reference< drawing::XShapes >& xTarget + , const uno::Reference< lang::XMultiServiceFactory>& xShapeFactory + ) +{ + try + { + uno::Reference< beans::XPropertySet > xModelPage( rChartModel.getPageBackground()); + if( ! xModelPage.is()) + return; + + if( !xShapeFactory.is() ) + return; + + //format page + tPropertyNameValueMap aNameValueMap; + PropertyMapper::getValueMap( aNameValueMap, PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xModelPage ); + + OUString aCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_PAGE, OUString() ) ); + aNameValueMap.emplace( "Name", uno::Any( aCID ) ); //CID OUString + + tNameSequence aNames; + tAnySequence aValues; + PropertyMapper::getMultiPropertyListsFromValueMap( aNames, aValues, aNameValueMap ); + + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + pShapeFactory->createRectangle( + xTarget, rPageSize, awt::Point(0, 0), aNames, aValues); + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + } +} + +void lcl_removeEmptyGroupShapes( const Reference< drawing::XShapes>& xParent ) +{ + if(!xParent.is()) + return; + Reference< drawing::XShapeGroup > xParentGroup( xParent, uno::UNO_QUERY ); + if( !xParentGroup.is() ) + { + Reference< drawing::XDrawPage > xPage( xParent, uno::UNO_QUERY ); + if( !xPage.is() ) + return; + } + + //iterate from back! + for( sal_Int32 nN = xParent->getCount(); nN--; ) + { + uno::Any aAny = xParent->getByIndex( nN ); + Reference< drawing::XShapes> xShapes; + if( aAny >>= xShapes ) + lcl_removeEmptyGroupShapes( xShapes ); + if( xShapes.is() && xShapes->getCount()==0 ) + { + //remove empty group shape + Reference< drawing::XShapeGroup > xGroup( xShapes, uno::UNO_QUERY ); + Reference< drawing::XShape > xShape( xShapes, uno::UNO_QUERY ); + if( xGroup.is() ) + xParent->remove( xShape ); + } + } +} + +} + +void ChartView::impl_refreshAddIn() +{ + if( !m_bRefreshAddIn ) + return; + + uno::Reference< beans::XPropertySet > xProp( static_cast< ::cppu::OWeakObject* >( &mrChartModel ), uno::UNO_QUERY ); + if( !xProp.is()) + return; + + try + { + uno::Reference< util::XRefreshable > xAddIn; + xProp->getPropertyValue( "AddIn" ) >>= xAddIn; + if( xAddIn.is() ) + { + bool bRefreshAddInAllowed = true; + xProp->getPropertyValue( "RefreshAddInAllowed" ) >>= bRefreshAddInAllowed; + if( bRefreshAddInAllowed ) + xAddIn->refresh(); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } +} + +static const char* envChartDummyFactory = getenv("CHART_DUMMY_FACTORY"); + +void ChartView::createShapes() +{ + SolarMutexGuard aSolarGuard; + + osl::MutexGuard aTimedGuard(maTimeMutex); + if(mrChartModel.isTimeBased()) + { + maTimeBased.bTimeBased = true; + } + + //make sure add-in is refreshed after creating the shapes + const ::comphelper::ScopeGuard aGuard( [this]() { this->impl_refreshAddIn(); } ); + + m_aResultingDiagramRectangleExcludingAxes = awt::Rectangle(0,0,0,0); + impl_deleteCoordinateSystems(); + if( m_pDrawModelWrapper ) + { + // #i12587# support for shapes in chart + m_pDrawModelWrapper->getSdrModel().EnableUndo( false ); + m_pDrawModelWrapper->clearMainDrawPage(); + } + + lcl_setDefaultWritingMode( m_pDrawModelWrapper, mrChartModel ); + + awt::Size aPageSize = mrChartModel.getVisualAreaSize( embed::Aspects::MSOLE_CONTENT ); + + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory); + if(!mxRootShape.is()) + mxRootShape = pShapeFactory->getOrCreateChartRootShape( m_xDrawPage ); + + SdrPage* pPage = ChartView::getSdrPage(); + if(pPage) //it is necessary to use the implementation here as the uno page does not provide a propertyset + pPage->SetSize(Size(aPageSize.Width,aPageSize.Height)); + else + { + OSL_FAIL("could not set page size correctly"); + } + ShapeFactory::setPageSize(mxRootShape, aPageSize); + + createShapes2D(aPageSize); + + // #i12587# support for shapes in chart + if ( m_pDrawModelWrapper ) + { + m_pDrawModelWrapper->getSdrModel().EnableUndo( true ); + } + + if(maTimeBased.bTimeBased) + { + maTimeBased.nFrame++; + } +} + +// util::XEventListener (base of XCloseListener) +void SAL_CALL ChartView::disposing( const lang::EventObject& /* rSource */ ) +{ +} + +void ChartView::impl_updateView( bool bCheckLockedCtrler ) +{ + if( !m_pDrawModelWrapper ) + return; + + // #i12587# support for shapes in chart + if ( m_bSdrViewIsInEditMode ) + { + return; + } + + if (bCheckLockedCtrler && mrChartModel.hasControllersLocked()) + return; + + if( !(m_bViewDirty && !m_bInViewUpdate) ) + return; + + m_bInViewUpdate = true; + //bool bOldRefreshAddIn = m_bRefreshAddIn; + //m_bRefreshAddIn = false; + try + { + impl_notifyModeChangeListener("invalid"); + + //prepare draw model + { + SolarMutexGuard aSolarGuard; + m_pDrawModelWrapper->lockControllers(); + } + + //create chart view + { + m_bViewDirty = false; + m_bViewUpdatePending = false; + createShapes(); + + if( m_bViewDirty ) + { + //avoid recursions due to add-in + m_bRefreshAddIn = false; + m_bViewDirty = false; + m_bViewUpdatePending = false; + //delete old chart view + createShapes(); + m_bRefreshAddIn = true; + } + } + + m_bViewDirty = m_bViewUpdatePending; + m_bViewUpdatePending = false; + m_bInViewUpdate = false; + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + m_bViewDirty = m_bViewUpdatePending; + m_bViewUpdatePending = false; + m_bInViewUpdate = false; + } + + { + SolarMutexGuard aSolarGuard; + m_pDrawModelWrapper->unlockControllers(); + } + + impl_notifyModeChangeListener("valid"); + + //m_bRefreshAddIn = bOldRefreshAddIn; +} + +// ____ XModifyListener ____ +void SAL_CALL ChartView::modified( const lang::EventObject& /* aEvent */ ) +{ + m_bViewDirty = true; + if( m_bInViewUpdate ) + m_bViewUpdatePending = true; + + impl_notifyModeChangeListener("dirty"); +} + +//SfxListener +void ChartView::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) +{ + //#i77362 change notification for changes on additional shapes are missing + if( m_bInViewUpdate ) + return; + + // #i12587# support for shapes in chart + if ( m_bSdrViewIsInEditMode ) + { + uno::Reference< view::XSelectionSupplier > xSelectionSupplier( mrChartModel.getCurrentController(), uno::UNO_QUERY ); + if ( xSelectionSupplier.is() ) + { + OUString aSelObjCID; + uno::Any aSelObj( xSelectionSupplier->getSelection() ); + aSelObj >>= aSelObjCID; + if ( !aSelObjCID.isEmpty() ) + { + return; + } + } + } + + if (rHint.GetId() != SfxHintId::ThisIsAnSdrHint) + return; + const SdrHint* pSdrHint = static_cast< const SdrHint* >(&rHint); + + bool bShapeChanged = false; + switch( pSdrHint->GetKind() ) + { + case SdrHintKind::ObjectChange: + bShapeChanged = true; + break; + case SdrHintKind::ObjectInserted: + bShapeChanged = true; + break; + case SdrHintKind::ObjectRemoved: + bShapeChanged = true; + break; + case SdrHintKind::ModelCleared: + bShapeChanged = true; + break; + case SdrHintKind::EndEdit: + bShapeChanged = true; + break; + default: + break; + } + + if(bShapeChanged) + { + //#i76053# do not send view modified notifications for changes on the hidden page which contains e.g. the symbols for the dialogs + if( ChartView::getSdrPage() != pSdrHint->GetPage() ) + bShapeChanged=false; + } + + if(!bShapeChanged) + return; + + mrChartModel.setModified(true); +} + +void ChartView::impl_notifyModeChangeListener( const OUString& rNewMode ) +{ + try + { + ::cppu::OInterfaceContainerHelper* pIC = m_aListenerContainer + .getContainer( cppu::UnoType<util::XModeChangeListener>::get()); + if( pIC ) + { + util::ModeChangeEvent aEvent( static_cast< uno::XWeak* >( this ), rNewMode ); + ::cppu::OInterfaceIteratorHelper aIt( *pIC ); + while( aIt.hasMoreElements() ) + { + uno::Reference< util::XModeChangeListener > xListener( aIt.next(), uno::UNO_QUERY ); + if( xListener.is() ) + xListener->modeChanged( aEvent ); + } + } + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } +} + +// ____ XModeChangeBroadcaster ____ + +void SAL_CALL ChartView::addModeChangeListener( const uno::Reference< util::XModeChangeListener >& xListener ) +{ + m_aListenerContainer.addInterface( + cppu::UnoType<util::XModeChangeListener>::get(), xListener ); +} +void SAL_CALL ChartView::removeModeChangeListener( const uno::Reference< util::XModeChangeListener >& xListener ) +{ + m_aListenerContainer.removeInterface( + cppu::UnoType<util::XModeChangeListener>::get(), xListener ); +} +void SAL_CALL ChartView::addModeChangeApproveListener( const uno::Reference< util::XModeChangeApproveListener >& /* _rxListener */ ) +{ + +} +void SAL_CALL ChartView::removeModeChangeApproveListener( const uno::Reference< util::XModeChangeApproveListener >& /* _rxListener */ ) +{ + +} + +// ____ XUpdatable ____ +void SAL_CALL ChartView::update() +{ + impl_updateView(); + + //#i100778# migrate all imported or old documents to a plot area sizing exclusive axes (in case the save settings allow for this): + //Although in general it is a bad idea to change the model from within the view this is exceptionally the best place to do this special conversion. + //When a view update is requested (what happens for creating the metafile or displaying + //the chart in edit mode or printing) it is most likely that all necessary information is available - like the underlying spreadsheet data for example. + //Those data are important for the correct axis label sizes which are needed during conversion. + if( DiagramHelper::switchDiagramPositioningToExcludingPositioning( mrChartModel, true, false ) ) + impl_updateView(); +} + +void SAL_CALL ChartView::updateSoft() +{ + update(); +} + +void SAL_CALL ChartView::updateHard() +{ + impl_updateView(false); +} + +// ____ XPropertySet ____ +Reference< beans::XPropertySetInfo > SAL_CALL ChartView::getPropertySetInfo() +{ + OSL_FAIL("not implemented"); + return nullptr; +} + +void SAL_CALL ChartView::setPropertyValue( const OUString& rPropertyName + , const Any& rValue ) +{ + if( rPropertyName == "Resolution" ) + { + awt::Size aNewResolution; + if( ! (rValue >>= aNewResolution) ) + throw lang::IllegalArgumentException( "Property 'Resolution' requires value of type awt::Size", nullptr, 0 ); + + if( m_aPageResolution.Width!=aNewResolution.Width || m_aPageResolution.Height!=aNewResolution.Height ) + { + //set modified only when the new resolution is higher and points were skipped before + bool bSetModified = m_bPointsWereSkipped && (m_aPageResolution.Width<aNewResolution.Width || m_aPageResolution.Height<aNewResolution.Height); + + m_aPageResolution = aNewResolution; + + if( bSetModified ) + this->modified( lang::EventObject( static_cast< uno::XWeak* >( this ) ) ); + } + } + else if( rPropertyName == "ZoomFactors" ) + { + //#i75867# poor quality of ole's alternative view with 3D scenes and zoomfactors besides 100% + uno::Sequence< beans::PropertyValue > aZoomFactors; + if( ! (rValue >>= aZoomFactors) ) + throw lang::IllegalArgumentException( "Property 'ZoomFactors' requires value of type Sequence< PropertyValue >", nullptr, 0 ); + + sal_Int32 nFilterArgs = aZoomFactors.getLength(); + beans::PropertyValue* pDataValues = aZoomFactors.getArray(); + while( nFilterArgs-- ) + { + if ( pDataValues->Name == "ScaleXNumerator" ) + pDataValues->Value >>= m_nScaleXNumerator; + else if ( pDataValues->Name == "ScaleXDenominator" ) + pDataValues->Value >>= m_nScaleXDenominator; + else if ( pDataValues->Name == "ScaleYNumerator" ) + pDataValues->Value >>= m_nScaleYNumerator; + else if ( pDataValues->Name == "ScaleYDenominator" ) + pDataValues->Value >>= m_nScaleYDenominator; + + pDataValues++; + } + } + else if( rPropertyName == "SdrViewIsInEditMode" ) + { + //#i77362 change notification for changes on additional shapes are missing + if( ! (rValue >>= m_bSdrViewIsInEditMode) ) + throw lang::IllegalArgumentException( "Property 'SdrViewIsInEditMode' requires value of type sal_Bool", nullptr, 0 ); + } + else + throw beans::UnknownPropertyException( "unknown property was tried to set to chart wizard " + rPropertyName, nullptr ); +} + +Any SAL_CALL ChartView::getPropertyValue( const OUString& rPropertyName ) +{ + if( rPropertyName != "Resolution" ) + throw beans::UnknownPropertyException( "unknown property was tried to get from chart wizard " + rPropertyName, nullptr ); + + return Any(m_aPageResolution); +} + +void SAL_CALL ChartView::addPropertyChangeListener( + const OUString& /* aPropertyName */, const Reference< beans::XPropertyChangeListener >& /* xListener */ ) +{ + OSL_FAIL("not implemented"); +} +void SAL_CALL ChartView::removePropertyChangeListener( + const OUString& /* aPropertyName */, const Reference< beans::XPropertyChangeListener >& /* aListener */ ) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL ChartView::addVetoableChangeListener( const OUString& /* PropertyName */, const Reference< beans::XVetoableChangeListener >& /* aListener */ ) +{ + OSL_FAIL("not implemented"); +} + +void SAL_CALL ChartView::removeVetoableChangeListener( const OUString& /* PropertyName */, const Reference< beans::XVetoableChangeListener >& /* aListener */ ) +{ + OSL_FAIL("not implemented"); +} + +// ____ XMultiServiceFactory ____ + +Reference< uno::XInterface > ChartView::createInstance( const OUString& aServiceSpecifier ) +{ + SolarMutexGuard aSolarGuard; + + SdrModel* pModel = ( m_pDrawModelWrapper ? &m_pDrawModelWrapper->getSdrModel() : nullptr ); + if ( pModel ) + { + if ( aServiceSpecifier == "com.sun.star.drawing.DashTable" ) + { + if ( !m_xDashTable.is() ) + { + m_xDashTable = SvxUnoDashTable_createInstance( pModel ); + } + return m_xDashTable; + } + else if ( aServiceSpecifier == "com.sun.star.drawing.GradientTable" ) + { + if ( !m_xGradientTable.is() ) + { + m_xGradientTable = SvxUnoGradientTable_createInstance( pModel ); + } + return m_xGradientTable; + } + else if ( aServiceSpecifier == "com.sun.star.drawing.HatchTable" ) + { + if ( !m_xHatchTable.is() ) + { + m_xHatchTable = SvxUnoHatchTable_createInstance( pModel ); + } + return m_xHatchTable; + } + else if ( aServiceSpecifier == "com.sun.star.drawing.BitmapTable" ) + { + if ( !m_xBitmapTable.is() ) + { + m_xBitmapTable = SvxUnoBitmapTable_createInstance( pModel ); + } + return m_xBitmapTable; + } + else if ( aServiceSpecifier == "com.sun.star.drawing.TransparencyGradientTable" ) + { + if ( !m_xTransGradientTable.is() ) + { + m_xTransGradientTable = SvxUnoTransGradientTable_createInstance( pModel ); + } + return m_xTransGradientTable; + } + else if ( aServiceSpecifier == "com.sun.star.drawing.MarkerTable" ) + { + if ( !m_xMarkerTable.is() ) + { + m_xMarkerTable = SvxUnoMarkerTable_createInstance( pModel ); + } + return m_xMarkerTable; + } + } + + return nullptr; +} + +Reference< uno::XInterface > ChartView::createInstanceWithArguments( const OUString& ServiceSpecifier, const uno::Sequence< uno::Any >& Arguments ) +{ + OSL_ENSURE( Arguments.hasElements(), "ChartView::createInstanceWithArguments: arguments are ignored" ); + return createInstance( ServiceSpecifier ); +} + +uno::Sequence< OUString > ChartView::getAvailableServiceNames() +{ + uno::Sequence< OUString > aServiceNames( 6 ); + + aServiceNames[0] = "com.sun.star.drawing.DashTable"; + aServiceNames[1] = "com.sun.star.drawing.GradientTable"; + aServiceNames[2] = "com.sun.star.drawing.HatchTable"; + aServiceNames[3] = "com.sun.star.drawing.BitmapTable"; + aServiceNames[4] = "com.sun.star.drawing.TransparencyGradientTable"; + aServiceNames[5] = "com.sun.star.drawing.MarkerTable"; + + return aServiceNames; +} + +OUString ChartView::dump() +{ +#if HAVE_FEATURE_DESKTOP + // Used for unit tests and in chartcontroller only, no need to drag in this when cross-compiling + // for non-desktop + impl_updateView(); + uno::Reference< drawing::XShapes > xShapes( m_xDrawPage, uno::UNO_QUERY_THROW ); + sal_Int32 n = xShapes->getCount(); + OUStringBuffer aBuffer; + for(sal_Int32 i = 0; i < n; ++i) + { + uno::Reference< drawing::XShapes > xShape(xShapes->getByIndex(i), uno::UNO_QUERY); + if(xShape.is()) + { + OUString aString = XShapeDumper::dump(mxRootShape); + aBuffer.append(aString); + } + else + { + uno::Reference< drawing::XShape > xSingleShape(xShapes->getByIndex(i), uno::UNO_QUERY); + if(!xSingleShape.is()) + continue; + OUString aString = XShapeDumper::dump(xSingleShape); + aBuffer.append(aString); + } + aBuffer.append("\n\n"); + } + + return aBuffer.makeStringAndClear(); +#else + return OUString(); +#endif +} + +void ChartView::setViewDirty() +{ + osl::MutexGuard aGuard(maTimeMutex); + m_bViewDirty = true; +} + +IMPL_LINK_NOARG(ChartView, UpdateTimeBased, Timer *, void) +{ + setViewDirty(); + update(); +} + +void ChartView::createShapes2D( const awt::Size& rPageSize ) +{ + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory); + + // todo: it would be nicer to just pass the page m_xDrawPage and format it, + // but the draw page does not support XPropertySet + formatPage( mrChartModel, rPageSize, mxRootShape, m_xShapeFactory ); + + CreateShapeParam2D aParam; + aParam.maRemainingSpace.X = 0; + aParam.maRemainingSpace.Y = 0; + aParam.maRemainingSpace.Width = rPageSize.Width; + aParam.maRemainingSpace.Height = rPageSize.Height; + + //create the group shape for diagram and axes first to have title and legends on top of it + uno::Reference< XDiagram > xDiagram( mrChartModel.getFirstDiagram() ); + uno::Reference< beans::XPropertySet > xProp(xDiagram, uno::UNO_QUERY); + bool bHasRelativeSize = false; + if( xProp.is() && xProp->getPropertyValue("RelativeSize").hasValue() ) + bHasRelativeSize = true; + + OUString aDiagramCID( ObjectIdentifier::createClassifiedIdentifier( OBJECTTYPE_DIAGRAM, OUString::number( 0 ) ) );//todo: other index if more than one diagram is possible + uno::Reference< drawing::XShapes > xDiagramPlusAxesPlusMarkHandlesGroup_Shapes( + pShapeFactory->createGroup2D(mxRootShape,aDiagramCID) ); + + aParam.mxMarkHandles = pShapeFactory->createInvisibleRectangle( + xDiagramPlusAxesPlusMarkHandlesGroup_Shapes, awt::Size(0,0)); + ShapeFactory::setShapeName(aParam.mxMarkHandles, "MarkHandles"); + + aParam.mxPlotAreaWithAxes = pShapeFactory->createInvisibleRectangle( + xDiagramPlusAxesPlusMarkHandlesGroup_Shapes, awt::Size(0, 0)); + ShapeFactory::setShapeName(aParam.mxPlotAreaWithAxes, "PlotAreaIncludingAxes"); + + aParam.mxDiagramWithAxesShapes = pShapeFactory->createGroup2D(xDiagramPlusAxesPlusMarkHandlesGroup_Shapes); + + bool bAutoPositionDummy = true; + + // create buttons + lcl_createButtons(mxRootShape, m_xShapeFactory, mrChartModel, aParam.maRemainingSpace); + + lcl_createTitle( + TitleHelper::MAIN_TITLE, mxRootShape, m_xShapeFactory, mrChartModel, + aParam.maRemainingSpace, rPageSize, ALIGN_TOP, bAutoPositionDummy); + if (!bHasRelativeSize && (aParam.maRemainingSpace.Width <= 0 || aParam.maRemainingSpace.Height <= 0)) + return; + + lcl_createTitle( + TitleHelper::SUB_TITLE, mxRootShape, m_xShapeFactory, mrChartModel, + aParam.maRemainingSpace, rPageSize, ALIGN_TOP, bAutoPositionDummy ); + if (!bHasRelativeSize && (aParam.maRemainingSpace.Width <= 0 || aParam.maRemainingSpace.Height <= 0)) + return; + + aParam.mpSeriesPlotterContainer = std::make_shared<SeriesPlotterContainer>(m_aVCooSysList); + aParam.mpSeriesPlotterContainer->initializeCooSysAndSeriesPlotter( mrChartModel ); + if(maTimeBased.bTimeBased && maTimeBased.nFrame != 0) + { + SeriesPlottersType& rSeriesPlotter = aParam.mpSeriesPlotterContainer->getSeriesPlotterList(); + size_t n = rSeriesPlotter.size(); + for(size_t i = 0; i < n; ++i) + { + std::vector<VDataSeries*> aAllNewDataSeries = rSeriesPlotter[i]->getAllSeries(); + std::vector< VDataSeries* >& rAllOldDataSeries = + maTimeBased.m_aDataSeriesList[i]; + size_t m = std::min(aAllNewDataSeries.size(), rAllOldDataSeries.size()); + for(size_t j = 0; j < m; ++j) + { + aAllNewDataSeries[j]->setOldTimeBased( + rAllOldDataSeries[j], (maTimeBased.nFrame % 60)/60.0); + } + } + } + + lcl_createLegend( + LegendHelper::getLegend( mrChartModel ), mxRootShape, m_xShapeFactory, m_xCC, + aParam.maRemainingSpace, rPageSize, mrChartModel, aParam.mpSeriesPlotterContainer->getLegendEntryProviderList(), + lcl_getDefaultWritingModeFromPool( m_pDrawModelWrapper ) ); + + if (!bHasRelativeSize && (aParam.maRemainingSpace.Width <= 0 || aParam.maRemainingSpace.Height <= 0)) + return; + + if (!createAxisTitleShapes2D(aParam, rPageSize, bHasRelativeSize)) + return; + + bool bDummy = false; + bool bIsVertical = DiagramHelper::getVertical(xDiagram, bDummy, bDummy); + + if (getAvailablePosAndSizeForDiagram(aParam, rPageSize, xProp)) + { + awt::Rectangle aUsedOuterRect = impl_createDiagramAndContent(aParam, rPageSize); + + if (aParam.mxPlotAreaWithAxes.is()) + { + aParam.mxPlotAreaWithAxes->setPosition(awt::Point(aUsedOuterRect.X, aUsedOuterRect.Y)); + aParam.mxPlotAreaWithAxes->setSize(awt::Size(aUsedOuterRect.Width, aUsedOuterRect.Height)); + } + + //correct axis title position + awt::Rectangle aDiagramPlusAxesRect( aUsedOuterRect ); + if (aParam.mbAutoPosTitleX) + changePositionOfAxisTitle(aParam.mpVTitleX.get(), ALIGN_BOTTOM, aDiagramPlusAxesRect, rPageSize); + if (aParam.mbAutoPosTitleY) + changePositionOfAxisTitle(aParam.mpVTitleY.get(), ALIGN_LEFT, aDiagramPlusAxesRect, rPageSize); + if (aParam.mbAutoPosTitleZ) + changePositionOfAxisTitle(aParam.mpVTitleZ.get(), ALIGN_Z, aDiagramPlusAxesRect, rPageSize); + if (aParam.mbAutoPosSecondTitleX) + changePositionOfAxisTitle(aParam.mpVTitleSecondX.get(), bIsVertical? ALIGN_RIGHT : ALIGN_TOP, aDiagramPlusAxesRect, rPageSize); + if (aParam.mbAutoPosSecondTitleY) + changePositionOfAxisTitle(aParam.mpVTitleSecondY.get(), bIsVertical? ALIGN_TOP : ALIGN_RIGHT, aDiagramPlusAxesRect, rPageSize); + } + + //cleanup: remove all empty group shapes to avoid grey border lines: + lcl_removeEmptyGroupShapes( mxRootShape ); + + if(maTimeBased.bTimeBased && maTimeBased.nFrame % 60 == 0) + { + // create copy of the data for next frame + SeriesPlottersType& rSeriesPlotter = aParam.mpSeriesPlotterContainer->getSeriesPlotterList(); + size_t n = rSeriesPlotter.size(); + maTimeBased.m_aDataSeriesList.clear(); + maTimeBased.m_aDataSeriesList.resize(n); + for(size_t i = 0; i < n; ++i) + { + std::vector<VDataSeries*> aAllNewDataSeries = rSeriesPlotter[i]->getAllSeries(); + std::vector<VDataSeries*>& rAllOldDataSeries = maTimeBased.m_aDataSeriesList[i]; + size_t m = aAllNewDataSeries.size(); + for(size_t j = 0; j < m; ++j) + { + rAllOldDataSeries.push_back( aAllNewDataSeries[j]-> + createCopyForTimeBased() ); + } + } + + maTimeBased.maTimer.Stop(); + } + + if(maTimeBased.bTimeBased && !maTimeBased.maTimer.IsActive()) + { + maTimeBased.maTimer.SetTimeout(15); + maTimeBased.maTimer.SetInvokeHandler(LINK(this, ChartView, UpdateTimeBased)); + maTimeBased.maTimer.Start(); + } +} + +bool ChartView::createAxisTitleShapes2D( CreateShapeParam2D& rParam, const css::awt::Size& rPageSize, bool bHasRelativeSize ) +{ + uno::Reference<XDiagram> xDiagram = mrChartModel.getFirstDiagram(); + + Reference< chart2::XChartType > xChartType( DiagramHelper::getChartTypeByIndex( xDiagram, 0 ) ); + sal_Int32 nDimension = DiagramHelper::getDimension( xDiagram ); + + if( ChartTypeHelper::isSupportingMainAxis( xChartType, nDimension, 0 ) ) + rParam.mpVTitleX = lcl_createTitle( TitleHelper::TITLE_AT_STANDARD_X_AXIS_POSITION, mxRootShape, m_xShapeFactory, mrChartModel + , rParam.maRemainingSpace, rPageSize, ALIGN_BOTTOM, rParam.mbAutoPosTitleX ); + if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0)) + return false; + + if( ChartTypeHelper::isSupportingMainAxis( xChartType, nDimension, 1 ) ) + rParam.mpVTitleY = lcl_createTitle( TitleHelper::TITLE_AT_STANDARD_Y_AXIS_POSITION, mxRootShape, m_xShapeFactory, mrChartModel + , rParam.maRemainingSpace, rPageSize, ALIGN_LEFT, rParam.mbAutoPosTitleY ); + if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0)) + return false; + + if( ChartTypeHelper::isSupportingMainAxis( xChartType, nDimension, 2 ) ) + rParam.mpVTitleZ = lcl_createTitle( TitleHelper::Z_AXIS_TITLE, mxRootShape, m_xShapeFactory, mrChartModel + , rParam.maRemainingSpace, rPageSize, ALIGN_RIGHT, rParam.mbAutoPosTitleZ ); + if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0)) + return false; + + bool bDummy = false; + bool bIsVertical = DiagramHelper::getVertical( xDiagram, bDummy, bDummy ); + + if( ChartTypeHelper::isSupportingSecondaryAxis( xChartType, nDimension ) ) + rParam.mpVTitleSecondX = lcl_createTitle( TitleHelper::SECONDARY_X_AXIS_TITLE, mxRootShape, m_xShapeFactory, mrChartModel + , rParam.maRemainingSpace, rPageSize, bIsVertical? ALIGN_RIGHT : ALIGN_TOP, rParam.mbAutoPosSecondTitleX ); + if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0)) + return false; + + if( ChartTypeHelper::isSupportingSecondaryAxis( xChartType, nDimension ) ) + rParam.mpVTitleSecondY = lcl_createTitle( TitleHelper::SECONDARY_Y_AXIS_TITLE, mxRootShape, m_xShapeFactory, mrChartModel + , rParam.maRemainingSpace, rPageSize, bIsVertical? ALIGN_TOP : ALIGN_RIGHT, rParam.mbAutoPosSecondTitleY ); + if (!bHasRelativeSize && (rParam.maRemainingSpace.Width <= 0 || rParam.maRemainingSpace.Height <= 0)) + return false; + + return true; +} + +} //namespace chart + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_chart2_ChartView_get_implementation(css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + ::chart::ChartModel *pChartModel = new ::chart::ChartModel(context); + return cppu::acquire(new ::chart::ChartView(context, *pChartModel)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/Clipping.cxx b/chart2/source/view/main/Clipping.cxx new file mode 100644 index 000000000..463b74975 --- /dev/null +++ b/chart2/source/view/main/Clipping.cxx @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <Clipping.hxx> +#include <CommonConverters.hxx> +#include <BaseGFXHelper.hxx> + +#include <osl/diagnose.h> + +#include <com/sun/star/drawing/Position3D.hpp> +#include <com/sun/star/drawing/DoubleSequence.hpp> + +namespace chart +{ +using namespace ::com::sun::star; +using ::basegfx::B2DRectangle; +using ::basegfx::B2DTuple; + +namespace{ +/** @descr This is a supporting function for lcl_clip2d. It computes a new parametric + value for an entering (dTE) or leaving (dTL) intersection point with one + of the edges bounding the clipping area. + For explanation of the parameters please refer to : + + Liang-Biarsky parametric line-clipping algorithm as described in: + Computer Graphics: principles and practice, 2nd ed., + James D. Foley et al., + Section 3.12.4 on page 117. +*/ +bool lcl_CLIPt(double fDenom,double fNum, double & fTE, double & fTL) +{ + double fT; + + if (fDenom > 0) // Intersection enters: PE + { + fT = fNum / fDenom; // Parametric value at the intersection. + if (fT > fTL) // fTE and fTL crossover + return false; // therefore reject the line. + else if (fT > fTE) // A new fTE has been found. + fTE = fT; + } + else if (fDenom < 0) // Intersection leaves: PL + { + fT = fNum / fDenom; // Parametric Value at the intersection. + if (fT < fTE) // fTE and fTL crossover + return false; // therefore reject the line. + else if (fT < fTL) // A new fTL has been found. + fTL = fT; + } + else if (fNum > 0) + return false; // Line lies on the outside of the edge. + + return true; +} + +/** @descr The line given by its two endpoints rP0 and rP1 is clipped at the rectangle + rRectangle. If there is at least a part of it visible then sal_True is returned and + the endpoints of that part are stored in rP0 and rP1. The points rP0 and rP1 + may have the same coordinates. + @param rP0 Start point of the line to clip. Modified to contain a start point inside + the clipping area if possible. + @param rP1 End point of the line to clip. Modified to contain an end point inside + the clipping area if possible. + @param rRectangle Clipping area. + @return If the line lies completely or partly inside the clipping area then TRUE + is returned. If the line lies completely outside then sal_False is returned and rP0 and + rP1 are left unmodified. +*/ +bool lcl_clip2d(B2DTuple& rPoint0, B2DTuple& rPoint1, const B2DRectangle& rRectangle) +{ + //Direction vector of the line. + B2DTuple aDirection = rPoint1 - rPoint0; + + if( aDirection.getX()==0 && aDirection.getY()==0 && rRectangle.isInside(rPoint0) ) + { + // Degenerate case of a zero length line. + return true; + } + else + { + // Values of the line parameter where the line enters resp. leaves the rectangle. + double fTE = 0, + fTL = 1; + + // Test whether at least a part lies in the four half-planes with respect to + // the rectangles four edges. + if( lcl_CLIPt(aDirection.getX(), rRectangle.getMinX() - rPoint0.getX(), fTE, fTL) ) + if( lcl_CLIPt(-aDirection.getX(), rPoint0.getX() - rRectangle.getMaxX(), fTE, fTL) ) + if( lcl_CLIPt(aDirection.getY(), rRectangle.getMinY() - rPoint0.getY(), fTE, fTL) ) + if( lcl_CLIPt(-aDirection.getY(), rPoint0.getY() - rRectangle.getMaxY(), fTE, fTL) ) + { + // At least a part is visible. + if (fTL < 1) + { + // Compute the new end point. + rPoint1.setX( rPoint0.getX() + fTL * aDirection.getX() ); + rPoint1.setY( rPoint0.getY() + fTL * aDirection.getY() ); + } + if (fTE > 0) + { + // Compute the new starting point. + rPoint0.setX( rPoint0.getX() + fTE * aDirection.getX() ); + rPoint0.setY( rPoint0.getY() + fTE * aDirection.getY() ); + } + return true; + } + + // Line is not visible. + return false; + } +} + +bool lcl_clip2d_(drawing::Position3D& rPoint0, drawing::Position3D& rPoint1, const B2DRectangle& rRectangle) +{ + B2DTuple aP0(rPoint0.PositionX,rPoint0.PositionY); + B2DTuple aP1(rPoint1.PositionX,rPoint1.PositionY); + bool bRet = lcl_clip2d( aP0, aP1, rRectangle ); + + rPoint0.PositionX = aP0.getX(); + rPoint0.PositionY = aP0.getY(); + rPoint1.PositionX = aP1.getX(); + rPoint1.PositionY = aP1.getY(); + + return bRet; +} + +unsigned int round_up_nearest_pow2(unsigned int v) +{ + // compute the next highest power of 2 of 32-bit v + --v; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + ++v; + return v; +} + +void lcl_addPointToPoly( drawing::PolyPolygonShape3D& rPoly + , const drawing::Position3D& rPos + , sal_Int32 nPolygonIndex + , std::vector< sal_Int32 >& rResultPointCount + , sal_Int32 nReservePointCount ) +{ + if(nPolygonIndex<0) + { + OSL_FAIL( "The polygon index needs to be > 0"); + nPolygonIndex=0; + } + + //make sure that we have enough polygons + if(nPolygonIndex >= rPoly.SequenceX.getLength() ) + { + rPoly.SequenceX.realloc(nPolygonIndex+1); + rPoly.SequenceY.realloc(nPolygonIndex+1); + rPoly.SequenceZ.realloc(nPolygonIndex+1); + rResultPointCount.resize(nPolygonIndex+1,0); + } + + drawing::DoubleSequence* pOuterSequenceX = &rPoly.SequenceX.getArray()[nPolygonIndex]; + drawing::DoubleSequence* pOuterSequenceY = &rPoly.SequenceY.getArray()[nPolygonIndex]; + drawing::DoubleSequence* pOuterSequenceZ = &rPoly.SequenceZ.getArray()[nPolygonIndex]; + + sal_Int32 nNewResultPointCount = rResultPointCount[nPolygonIndex]+1; + sal_Int32 nSeqLength = pOuterSequenceX->getLength(); + + if( nSeqLength <= nNewResultPointCount ) + { + sal_Int32 nReallocLength = nReservePointCount > SAL_MAX_INT16 ? round_up_nearest_pow2(nNewResultPointCount) * 2 : nReservePointCount; + if( nNewResultPointCount > nReallocLength ) + { + nReallocLength = nNewResultPointCount; + OSL_FAIL("this should not be the case to avoid performance problems"); + } + pOuterSequenceX->realloc(nReallocLength); + pOuterSequenceY->realloc(nReallocLength); + pOuterSequenceZ->realloc(nReallocLength); + } + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + pInnerSequenceX[nNewResultPointCount-1] = rPos.PositionX; + pInnerSequenceY[nNewResultPointCount-1] = rPos.PositionY; + pInnerSequenceZ[nNewResultPointCount-1] = rPos.PositionZ; + rResultPointCount[nPolygonIndex]=nNewResultPointCount; +} + +}//end anonymous namespace + +void Clipping::clipPolygonAtRectangle( const drawing::PolyPolygonShape3D& rPolygon + , const B2DRectangle& rRectangle + , drawing::PolyPolygonShape3D& aResult + , bool bSplitPiecesToDifferentPolygons ) +{ + aResult.SequenceX.realloc(0); + aResult.SequenceY.realloc(0); + aResult.SequenceZ.realloc(0); + + if(!rPolygon.SequenceX.hasElements()) + return; + + //need clipping?: + { + ::basegfx::B3DRange a3DRange( BaseGFXHelper::getBoundVolume( rPolygon ) ); + ::basegfx::B2DRange a2DRange( a3DRange.getMinX(), a3DRange.getMinY(), a3DRange.getMaxX(), a3DRange.getMaxY() ); + if( rRectangle.isInside( a2DRange ) ) + { + aResult = rPolygon; + return; + } + else + { + a2DRange.intersect( rRectangle ); + if( a2DRange.isEmpty() ) + return; + } + } + + std::vector< sal_Int32 > aResultPointCount;//per polygon index + + //apply clipping: + drawing::Position3D aFrom; + drawing::Position3D aTo; + + sal_Int32 nNewPolyIndex = 0; + sal_Int32 nOldPolyCount = rPolygon.SequenceX.getLength(); + for(sal_Int32 nOldPolyIndex=0; nOldPolyIndex<nOldPolyCount; nOldPolyIndex++, nNewPolyIndex++ ) + { + sal_Int32 nOldPointCount = rPolygon.SequenceX[nOldPolyIndex].getLength(); + + // set last point to a position outside the rectangle, such that the first + // time lcl_clip2d returns true, the comparison to last will always yield false + drawing::Position3D aLast(rRectangle.getMinX()-1.0,rRectangle.getMinY()-1.0, 0.0 ); + + for(sal_Int32 nOldPoint=1; nOldPoint<nOldPointCount; nOldPoint++) + { + aFrom = getPointFromPoly(rPolygon,nOldPoint-1,nOldPolyIndex); + aTo = getPointFromPoly(rPolygon,nOldPoint,nOldPolyIndex); + if( lcl_clip2d_(aFrom, aTo, rRectangle) ) + { + // compose a Polygon of as many consecutive points as possible + if(aFrom == aLast) + { + if( aTo != aFrom ) + { + lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount ); + } + } + else + { + if( bSplitPiecesToDifferentPolygons && nOldPoint!=1 ) + { + if( nNewPolyIndex < aResult.SequenceX.getLength() + && aResultPointCount[nNewPolyIndex]>0 ) + nNewPolyIndex++; + } + lcl_addPointToPoly( aResult, aFrom, nNewPolyIndex, aResultPointCount, nOldPointCount ); + if( aTo != aFrom ) + lcl_addPointToPoly( aResult, aTo, nNewPolyIndex, aResultPointCount, nOldPointCount ); + } + aLast = aTo; + } + } + } + //free unused space + for( sal_Int32 nPolygonIndex = aResultPointCount.size(); nPolygonIndex--; ) + { + drawing::DoubleSequence* pOuterSequenceX = &aResult.SequenceX.getArray()[nPolygonIndex]; + drawing::DoubleSequence* pOuterSequenceY = &aResult.SequenceY.getArray()[nPolygonIndex]; + drawing::DoubleSequence* pOuterSequenceZ = &aResult.SequenceZ.getArray()[nPolygonIndex]; + + sal_Int32 nUsedPointCount = aResultPointCount[nPolygonIndex]; + pOuterSequenceX->realloc(nUsedPointCount); + pOuterSequenceY->realloc(nUsedPointCount); + pOuterSequenceZ->realloc(nUsedPointCount); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/DataPointSymbolSupplier.cxx b/chart2/source/view/main/DataPointSymbolSupplier.cxx new file mode 100644 index 000000000..7f446139a --- /dev/null +++ b/chart2/source/view/main/DataPointSymbolSupplier.cxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <chartview/DataPointSymbolSupplier.hxx> +#include <ShapeFactory.hxx> +#include <com/sun/star/drawing/Position3D.hpp> + +namespace chart +{ +using namespace ::com::sun::star; + +uno::Reference< drawing::XShapes > DataPointSymbolSupplier::create2DSymbolList( + const uno::Reference< lang::XMultiServiceFactory >& xShapeFactory + , const uno::Reference< drawing::XShapes >& xTarget + , const drawing::Direction3D& rSize ) +{ + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + uno::Reference< drawing::XShapes > xGroupShapes = + pShapeFactory->createGroup2D( xTarget ); + + drawing::Position3D aPos(0,0,0); + for(sal_Int32 nS=0;nS<ShapeFactory::getSymbolCount();nS++) + { + pShapeFactory->createSymbol2D( xGroupShapes, aPos, rSize, nS, 0, 0 ); + } + return xGroupShapes; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/DrawModelWrapper.cxx b/chart2/source/view/main/DrawModelWrapper.cxx new file mode 100644 index 000000000..6403e0eea --- /dev/null +++ b/chart2/source/view/main/DrawModelWrapper.cxx @@ -0,0 +1,329 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <chartview/DrawModelWrapper.hxx> +#include <ShapeFactory.hxx> +#include "ChartItemPool.hxx" +#include <ObjectIdentifier.hxx> +#include <svx/unomodel.hxx> +#include <svl/itempool.hxx> +#include <svl/eitem.hxx> +#include <editeng/eeitem.hxx> +#include <svx/svx3ditems.hxx> +#include <svx/objfac3d.hxx> +#include <svx/svdpage.hxx> +#include <svx/xtable.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/unolingu.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> + +#include <com/sun/star/container/XChild.hpp> + +namespace com::sun::star::linguistic2 { class XHyphenator; } +namespace com::sun::star::linguistic2 { class XSpellChecker1; } + +using namespace ::com::sun::star; + + +namespace chart +{ + +DrawModelWrapper::DrawModelWrapper() +: SdrModel() + , m_pChartItemPool(nullptr) +{ + m_pChartItemPool = ChartItemPool::CreateChartItemPool(); + + SetScaleUnit(MapUnit::Map100thMM); + SetScaleFraction(Fraction(1, 1)); + SetDefaultFontHeight(423); // 12pt + + SfxItemPool* pMasterPool = &GetItemPool(); + pMasterPool->SetDefaultMetric(MapUnit::Map100thMM); + pMasterPool->SetPoolDefaultItem(SfxBoolItem(EE_PARA_HYPHENATE, true) ); + pMasterPool->SetPoolDefaultItem(makeSvx3DPercentDiagonalItem (5)); + + SfxItemPool* pPool = pMasterPool; + // append chart pool to end of pool chain + for (;;) + { + SfxItemPool* pSecondary = pPool->GetSecondaryPool(); + if (!pSecondary) + break; + + pPool = pSecondary; + } + pPool->SetSecondaryPool(m_pChartItemPool); + pMasterPool->FreezeIdRanges(); + + //this factory needs to be created before first use of 3D scenes once upon an office runtime + //@todo in future this should be done by drawing engine itself on demand + static bool b3dFactoryInitialized = false; + if(!b3dFactoryInitialized) + { + E3dObjFactory aObjFactory; + b3dFactoryInitialized = true; + } + + //Hyphenation and spellchecking + SdrOutliner& rOutliner = GetDrawOutliner(); + try + { + uno::Reference< linguistic2::XHyphenator > xHyphenator( LinguMgr::GetHyphenator() ); + if( xHyphenator.is() ) + rOutliner.SetHyphenator( xHyphenator ); + + uno::Reference< linguistic2::XSpellChecker1 > xSpellChecker( LinguMgr::GetSpellChecker() ); + if ( xSpellChecker.is() ) + rOutliner.SetSpeller( xSpellChecker ); + } + catch(...) + { + OSL_FAIL("Can't get Hyphenator or SpellChecker for chart"); + } + + //ref device for font rendering + OutputDevice* pDefaultDevice = rOutliner.GetRefDevice(); + if( !pDefaultDevice ) + pDefaultDevice = Application::GetDefaultDevice(); + m_pRefDevice.disposeAndClear(); + m_pRefDevice = VclPtr<VirtualDevice>::Create(*pDefaultDevice); + MapMode aMapMode = m_pRefDevice->GetMapMode(); + aMapMode.SetMapUnit(MapUnit::Map100thMM); + m_pRefDevice->SetMapMode(aMapMode); + SetRefDevice(m_pRefDevice.get()); + rOutliner.SetRefDevice(m_pRefDevice.get()); +} + +DrawModelWrapper::~DrawModelWrapper() +{ + //remove m_pChartItemPool from pool chain + if(m_pChartItemPool) + { + SfxItemPool* pPool = &GetItemPool(); + for (;;) + { + SfxItemPool* pSecondary = pPool->GetSecondaryPool(); + if(pSecondary == m_pChartItemPool) + { + pPool->SetSecondaryPool (nullptr); + break; + } + pPool = pSecondary; + } + SfxItemPool::Free(m_pChartItemPool); + } + m_pRefDevice.disposeAndClear(); +} + +uno::Reference< uno::XInterface > DrawModelWrapper::createUnoModel() +{ + uno::Reference< lang::XComponent > xComponent = new SvxUnoDrawingModel( this ); //tell Andreas Schluens if SvxUnoDrawingModel is not needed anymore -> remove export from svx to avoid link problems in writer + return uno::Reference< uno::XInterface >::query( xComponent ); +} + +uno::Reference< frame::XModel > DrawModelWrapper::getUnoModel() +{ + uno::Reference< uno::XInterface > xI = SdrModel::getUnoModel(); + return uno::Reference<frame::XModel>::query( xI ); +} + +SdrModel& DrawModelWrapper::getSdrModel() +{ + return *this; +} + +uno::Reference< lang::XMultiServiceFactory > DrawModelWrapper::getShapeFactory() +{ + uno::Reference< lang::XMultiServiceFactory > xShapeFactory( getUnoModel(), uno::UNO_QUERY ); + return xShapeFactory; +} + +uno::Reference< drawing::XDrawPage > const & DrawModelWrapper::getMainDrawPage() +{ + if (m_xMainDrawPage.is()) + return m_xMainDrawPage; + + // Create draw page. + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSuplier(getUnoModel(), uno::UNO_QUERY); + if (!xDrawPagesSuplier.is()) + return m_xMainDrawPage; + + uno::Reference<drawing::XDrawPages> xDrawPages = xDrawPagesSuplier->getDrawPages(); + if (xDrawPages->getCount() > 1) + { + // Take the first page in case of multiple pages. + uno::Any aPage = xDrawPages->getByIndex(0); + aPage >>= m_xMainDrawPage; + } + + if (!m_xMainDrawPage.is()) + { + m_xMainDrawPage = xDrawPages->insertNewByIndex(0); + } + + //ensure that additional shapes are in front of the chart objects so create the chart root before + // let us disable this call for now + // TODO:moggi + // ShapeFactory::getOrCreateShapeFactory(getShapeFactory())->getOrCreateChartRootShape( m_xMainDrawPage ); + return m_xMainDrawPage; +} +uno::Reference< drawing::XDrawPage > const & DrawModelWrapper::getHiddenDrawPage() +{ + if( !m_xHiddenDrawPage.is() ) + { + uno::Reference< drawing::XDrawPagesSupplier > xDrawPagesSuplier( getUnoModel(), uno::UNO_QUERY ); + if( xDrawPagesSuplier.is() ) + { + uno::Reference< drawing::XDrawPages > xDrawPages( xDrawPagesSuplier->getDrawPages () ); + if( xDrawPages->getCount()>1 ) + { + uno::Any aPage = xDrawPages->getByIndex( 1 ) ; + aPage >>= m_xHiddenDrawPage; + } + + if(!m_xHiddenDrawPage.is()) + { + if( xDrawPages->getCount()==0 ) + m_xMainDrawPage = xDrawPages->insertNewByIndex( 0 ); + m_xHiddenDrawPage = xDrawPages->insertNewByIndex( 1 ); + } + } + } + return m_xHiddenDrawPage; +} +void DrawModelWrapper::clearMainDrawPage() +{ + //uno::Reference<drawing::XShapes> xChartRoot( m_xMainDrawPage, uno::UNO_QUERY ); + uno::Reference<drawing::XShapes> xChartRoot( ShapeFactory::getChartRootShape( m_xMainDrawPage ) ); + if( xChartRoot.is() ) + { + sal_Int32 nSubCount = xChartRoot->getCount(); + uno::Reference< drawing::XShape > xShape; + for( sal_Int32 nS = nSubCount; nS--; ) + { + if( xChartRoot->getByIndex( nS ) >>= xShape ) + xChartRoot->remove( xShape ); + } + } +} + +uno::Reference< drawing::XShapes > DrawModelWrapper::getChartRootShape( + const uno::Reference< drawing::XDrawPage>& xDrawPage ) +{ + return ShapeFactory::getChartRootShape( xDrawPage ); +} + +void DrawModelWrapper::lockControllers() +{ + uno::Reference< frame::XModel > xDrawModel( getUnoModel() ); + if( xDrawModel.is()) + xDrawModel->lockControllers(); +} +void DrawModelWrapper::unlockControllers() +{ + uno::Reference< frame::XModel > xDrawModel( getUnoModel() ); + if( xDrawModel.is()) + xDrawModel->unlockControllers(); +} + +OutputDevice* DrawModelWrapper::getReferenceDevice() const +{ + return SdrModel::GetRefDevice(); +} + +SfxItemPool& DrawModelWrapper::GetItemPool() +{ + return SdrModel::GetItemPool(); +} +XColorListRef DrawModelWrapper::GetColorList() const +{ + return SdrModel::GetColorList(); +} +XDashListRef DrawModelWrapper::GetDashList() const +{ + return SdrModel::GetDashList(); +} +XLineEndListRef DrawModelWrapper::GetLineEndList() const +{ + return SdrModel::GetLineEndList(); +} +XGradientListRef DrawModelWrapper::GetGradientList() const +{ + return SdrModel::GetGradientList(); +} +XHatchListRef DrawModelWrapper::GetHatchList() const +{ + return SdrModel::GetHatchList(); +} +XBitmapListRef DrawModelWrapper::GetBitmapList() const +{ + return SdrModel::GetBitmapList(); +} + +XPatternListRef DrawModelWrapper::GetPatternList() const +{ + return SdrModel::GetPatternList(); +} + +SdrObject* DrawModelWrapper::getNamedSdrObject( const OUString& rName ) +{ + if( rName.isEmpty() ) + return nullptr; + return getNamedSdrObject( rName, GetPage(0) ); +} + +SdrObject* DrawModelWrapper::getNamedSdrObject( const OUString& rObjectCID, SdrObjList const * pSearchList ) +{ + if(!pSearchList || rObjectCID.isEmpty()) + return nullptr; + const size_t nCount = pSearchList->GetObjCount(); + for( size_t nN=0; nN<nCount; ++nN ) + { + SdrObject* pObj = pSearchList->GetObj(nN); + if(!pObj) + continue; + if( ObjectIdentifier::areIdenticalObjects( rObjectCID, pObj->GetName() ) ) + return pObj; + pObj = DrawModelWrapper::getNamedSdrObject( rObjectCID, pObj->GetSubList() ); + if(pObj) + return pObj; + } + return nullptr; +} + +bool DrawModelWrapper::removeShape( const uno::Reference< drawing::XShape >& xShape ) +{ + uno::Reference< container::XChild > xChild( xShape, uno::UNO_QUERY ); + if( xChild.is() ) + { + uno::Reference<drawing::XShapes> xShapes( xChild->getParent(), uno::UNO_QUERY ); + if( xShapes.is() ) + { + xShapes->remove(xShape); + return true; + } + } + return false; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/LabelPositionHelper.cxx b/chart2/source/view/main/LabelPositionHelper.cxx new file mode 100644 index 000000000..811bf7ad1 --- /dev/null +++ b/chart2/source/view/main/LabelPositionHelper.cxx @@ -0,0 +1,468 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <LabelPositionHelper.hxx> +#include <PlottingPositionHelper.hxx> +#include <PropertyMapper.hxx> +#include <RelativeSizeHelper.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/XShape.hpp> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +LabelPositionHelper::LabelPositionHelper( + sal_Int32 nDimensionCount + , const uno::Reference< drawing::XShapes >& xLogicTarget + , ShapeFactory* pShapeFactory ) + : m_nDimensionCount(nDimensionCount) + , m_xLogicTarget(xLogicTarget) + , m_pShapeFactory(pShapeFactory) +{ +} + +LabelPositionHelper::~LabelPositionHelper() +{ +} + +awt::Point LabelPositionHelper::transformSceneToScreenPosition( const drawing::Position3D& rScenePosition3D ) const +{ + return PlottingPositionHelper::transformSceneToScreenPosition( + rScenePosition3D, m_xLogicTarget, m_pShapeFactory, m_nDimensionCount ); +} + +void LabelPositionHelper::changeTextAdjustment( tAnySequence& rPropValues, const tNameSequence& rPropNames, LabelAlignment eAlignment) +{ + //HorizontalAdjustment + { + drawing::TextHorizontalAdjust eHorizontalAdjust = drawing::TextHorizontalAdjust_CENTER; + if( eAlignment==LABEL_ALIGN_RIGHT || eAlignment==LABEL_ALIGN_RIGHT_TOP || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM ) + eHorizontalAdjust = drawing::TextHorizontalAdjust_LEFT; + else if( eAlignment==LABEL_ALIGN_LEFT || eAlignment==LABEL_ALIGN_LEFT_TOP || eAlignment==LABEL_ALIGN_LEFT_BOTTOM ) + eHorizontalAdjust = drawing::TextHorizontalAdjust_RIGHT; + uno::Any* pHorizontalAdjustAny = PropertyMapper::getValuePointer(rPropValues,rPropNames,"TextHorizontalAdjust"); + if(pHorizontalAdjustAny) + *pHorizontalAdjustAny <<= eHorizontalAdjust; + } + + //VerticalAdjustment + { + drawing::TextVerticalAdjust eVerticalAdjust = drawing::TextVerticalAdjust_CENTER; + if( eAlignment==LABEL_ALIGN_TOP || eAlignment==LABEL_ALIGN_RIGHT_TOP || eAlignment==LABEL_ALIGN_LEFT_TOP ) + eVerticalAdjust = drawing::TextVerticalAdjust_BOTTOM; + else if( eAlignment==LABEL_ALIGN_BOTTOM || eAlignment==LABEL_ALIGN_RIGHT_BOTTOM || eAlignment==LABEL_ALIGN_LEFT_BOTTOM ) + eVerticalAdjust = drawing::TextVerticalAdjust_TOP; + uno::Any* pVerticalAdjustAny = PropertyMapper::getValuePointer(rPropValues,rPropNames,"TextVerticalAdjust"); + if(pVerticalAdjustAny) + *pVerticalAdjustAny <<= eVerticalAdjust; + } +} + +static void lcl_doDynamicFontResize( uno::Any* pAOldAndNewFontHeightAny + , const awt::Size& rOldReferenceSize + , const awt::Size& rNewReferenceSize ) +{ + double fOldFontHeight = 0; + if( pAOldAndNewFontHeightAny && ( *pAOldAndNewFontHeightAny >>= fOldFontHeight ) ) + { + double fNewFontHeight = RelativeSizeHelper::calculate( fOldFontHeight, rOldReferenceSize, rNewReferenceSize ); + *pAOldAndNewFontHeightAny <<= fNewFontHeight; + } +} + +void LabelPositionHelper::doDynamicFontResize( tAnySequence& rPropValues + , const tNameSequence& rPropNames + , const uno::Reference< beans::XPropertySet >& xAxisModelProps + , const awt::Size& rNewReferenceSize + ) +{ + //handle dynamic font resize: + awt::Size aOldReferenceSize; + if( xAxisModelProps->getPropertyValue( "ReferencePageSize") >>= aOldReferenceSize ) + { + uno::Any* pAOldAndNewFontHeightAny = PropertyMapper::getValuePointer( rPropValues, rPropNames, "CharHeight" ); + lcl_doDynamicFontResize( pAOldAndNewFontHeightAny, aOldReferenceSize, rNewReferenceSize ); + pAOldAndNewFontHeightAny = PropertyMapper::getValuePointer( rPropValues, rPropNames, "CharHeightAsian" ); + lcl_doDynamicFontResize( pAOldAndNewFontHeightAny, aOldReferenceSize, rNewReferenceSize ); + pAOldAndNewFontHeightAny = PropertyMapper::getValuePointer( rPropValues, rPropNames, "CharHeightComplex" ); + lcl_doDynamicFontResize( pAOldAndNewFontHeightAny, aOldReferenceSize, rNewReferenceSize ); + } +} + +namespace +{ + +void lcl_correctRotation_Left( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter ) +{ + //correct label positions for labels on a left side of something with a right centered alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree==0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfXCorrection = -aSize.Height*rtl::math::sin( fAnglePi )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = -aSize.Width*rtl::math::sin( fAnglePi )/2.0; + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = fAnglePi-F_PI2; + rfXCorrection = -aSize.Width *rtl::math::sin( beta ) + -aSize.Height *rtl::math::cos( beta )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = -aSize.Width *rtl::math::cos( beta )/2.0; + else + rfYCorrection = -aSize.Width *rtl::math::cos( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = fAnglePi - F_PI; + rfXCorrection = -aSize.Width *rtl::math::cos( beta ) + -aSize.Height*rtl::math::sin( beta )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = aSize.Width *rtl::math::sin( beta )/2.0; + else + rfYCorrection = aSize.Width *rtl::math::sin( beta ); + } + else + { + double beta = 2*F_PI - fAnglePi; + rfXCorrection = -aSize.Height*rtl::math::sin( beta )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = aSize.Width*rtl::math::sin( beta )/2.0; + } +} + +void lcl_correctRotation_Right( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter ) +{ + //correct label positions for labels on a right side of something with a left centered alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree== 0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfXCorrection = aSize.Height*rtl::math::sin( fAnglePi )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = aSize.Width*rtl::math::sin( fAnglePi )/2.0; + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = F_PI - fAnglePi; + rfXCorrection = aSize.Width *rtl::math::cos( beta ) + + aSize.Height*rtl::math::sin( beta )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = aSize.Width *rtl::math::sin( beta )/2.0; + else + rfYCorrection = aSize.Width *rtl::math::sin( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = 3*F_PI2 - fAnglePi; + rfXCorrection = aSize.Width *rtl::math::sin( beta ) + +aSize.Height*rtl::math::cos( beta )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = -aSize.Width *rtl::math::cos( beta )/2.0; + else + rfYCorrection = -aSize.Width *rtl::math::cos( beta ); + } + else + { + rfXCorrection = aSize.Height*rtl::math::sin( 2*F_PI - fAnglePi )/2.0; + if( bRotateAroundCenter ) + rfYCorrection = -aSize.Width*rtl::math::sin( 2*F_PI - fAnglePi )/2.0; + } +} + +void lcl_correctRotation_Top( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter ) +{ + //correct label positions for labels on top of something with a bottom centered alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree== 0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfXCorrection = aSize.Height*rtl::math::sin( fAnglePi )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection += aSize.Width*rtl::math::cos( fAnglePi )/2.0; + rfYCorrection = -aSize.Width*rtl::math::sin( fAnglePi )/2.0; + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = fAnglePi - F_PI2; + rfXCorrection = aSize.Height*rtl::math::cos( beta )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection -= aSize.Width*rtl::math::sin( beta )/2.0; + rfYCorrection = -aSize.Width*rtl::math::cos( beta )/2.0 + - aSize.Height*rtl::math::sin( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = fAnglePi - F_PI; + rfXCorrection = -aSize.Height *rtl::math::sin( beta )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection += aSize.Width *rtl::math::cos( beta )/2.0; + rfYCorrection = -aSize.Width *rtl::math::sin( beta )/2.0 + -aSize.Height *rtl::math::cos( beta ); + } + else + { + rfXCorrection = aSize.Height*rtl::math::sin( fAnglePi )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection -= aSize.Width*rtl::math::cos( fAnglePi )/2.0; + rfYCorrection = aSize.Width*rtl::math::sin( fAnglePi )/2.0; + } +} + +void lcl_correctRotation_Bottom( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize, bool bRotateAroundCenter ) +{ + //correct label positions for labels below something with a top centered alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree==0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfXCorrection = -aSize.Height*rtl::math::sin( fAnglePi )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection -= aSize.Width *rtl::math::cos( fAnglePi )/2.0; + rfYCorrection = aSize.Width*rtl::math::sin( fAnglePi )/2.0; + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = fAnglePi-F_PI2; + rfXCorrection = -aSize.Height*rtl::math::cos( beta )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection += aSize.Width *rtl::math::sin( beta )/2.0; + rfYCorrection = aSize.Width *rtl::math::cos( beta )/2.0 + +aSize.Height*rtl::math::sin( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = 3*F_PI2 - fAnglePi; + rfXCorrection = aSize.Height*rtl::math::cos( beta )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection -= aSize.Width *rtl::math::sin( beta )/2.0; + rfYCorrection = aSize.Height*rtl::math::sin( beta ) + +aSize.Width*rtl::math::cos( beta )/2.0; + } + else + { + double beta = 2*F_PI - fAnglePi; + rfXCorrection = aSize.Height*rtl::math::sin( beta )/2.0; + if( !bRotateAroundCenter ) + rfXCorrection += aSize.Width*rtl::math::cos( beta )/2.0; + rfYCorrection = aSize.Width*rtl::math::sin( beta )/2.0; + } +} + +void lcl_correctRotation_Left_Top( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize ) +{ + //correct position for labels at the left top corner of something with a bottom right alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree==0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfYCorrection = -aSize.Width*rtl::math::sin( fAnglePi ); + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = fAnglePi-F_PI2; + rfXCorrection = -aSize.Width*rtl::math::sin( beta ); + rfYCorrection = -aSize.Height*rtl::math::sin( beta ) + -aSize.Width*rtl::math::cos( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = 3*F_PI2 - fAnglePi; + rfXCorrection = -aSize.Height*rtl::math::cos( beta ) + -aSize.Width*rtl::math::sin( beta ); + rfYCorrection = -aSize.Height*rtl::math::sin( beta ); + } + else + { + rfXCorrection = aSize.Height*rtl::math::sin( fAnglePi ); + } +} + +void lcl_correctRotation_Left_Bottom( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize ) +{ + //correct position for labels at the left bottom corner of something with a top right alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree==0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfXCorrection = -aSize.Height*rtl::math::sin( fAnglePi ); + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = fAnglePi-F_PI2; + rfXCorrection = -aSize.Width*rtl::math::sin( beta ) + -aSize.Height*rtl::math::cos( beta ); + rfYCorrection = aSize.Height*rtl::math::sin( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = 3*F_PI2 - fAnglePi; + rfXCorrection = -aSize.Width*rtl::math::sin( beta ); + rfYCorrection = aSize.Width*rtl::math::cos( beta ) + +aSize.Height*rtl::math::sin( beta ); + } + else + { + rfYCorrection = -aSize.Width*rtl::math::sin( fAnglePi ); + } +} + +void lcl_correctRotation_Right_Top( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize ) +{ + //correct position for labels at the right top corner of something with a bottom left alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree==0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfXCorrection = aSize.Height*rtl::math::sin( fAnglePi ); + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = fAnglePi-F_PI2; + rfXCorrection = aSize.Width*rtl::math::sin( beta ) + +aSize.Height*rtl::math::cos( beta ); + rfYCorrection = -aSize.Height*rtl::math::sin( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = 3*F_PI2 - fAnglePi; + rfXCorrection = aSize.Width*rtl::math::sin( beta ); + rfYCorrection = -aSize.Width*rtl::math::cos( beta ) + -aSize.Height*rtl::math::sin( beta ); + } + else + { + rfYCorrection = aSize.Width*rtl::math::sin( fAnglePi ); + } +} + +void lcl_correctRotation_Right_Bottom( double& rfXCorrection, double& rfYCorrection + , double fAnglePositiveDegree, const awt::Size& aSize ) +{ + //correct position for labels at the right bottom corner of something with a top left alignment + double fAnglePi = basegfx::deg2rad(fAnglePositiveDegree); + if( fAnglePositiveDegree==0.0 ) + { + } + else if( fAnglePositiveDegree<= 90.0 ) + { + rfYCorrection = aSize.Width*rtl::math::sin( fAnglePi ); + } + else if( fAnglePositiveDegree<= 180.0 ) + { + double beta = fAnglePi-F_PI2; + rfXCorrection = aSize.Width*rtl::math::sin( beta ); + rfYCorrection = aSize.Height*rtl::math::sin( beta ) + +aSize.Width*rtl::math::cos( beta ); + } + else if( fAnglePositiveDegree<= 270.0 ) + { + double beta = 3*F_PI2 - fAnglePi; + rfXCorrection = aSize.Height*rtl::math::cos( beta ) + +aSize.Width*rtl::math::sin( beta ); + rfYCorrection = aSize.Height*rtl::math::sin( beta ); + } + else + { + rfXCorrection = -aSize.Height*rtl::math::sin( fAnglePi ); + } +} + +}//end anonymous namespace + +void LabelPositionHelper::correctPositionForRotation( const uno::Reference< drawing::XShape >& xShape2DText + , LabelAlignment eLabelAlignment, const double fRotationAngle, bool bRotateAroundCenter ) +{ + if( !xShape2DText.is() ) + return; + + awt::Point aOldPos = xShape2DText->getPosition(); + awt::Size aSize = xShape2DText->getSize(); + + double fYCorrection = 0.0; + double fXCorrection = 0.0; + + double fAnglePositiveDegree = fRotationAngle; + while(fAnglePositiveDegree<0.0) + fAnglePositiveDegree+=360.0; + + switch(eLabelAlignment) + { + case LABEL_ALIGN_LEFT: + lcl_correctRotation_Left( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter ); + break; + case LABEL_ALIGN_RIGHT: + lcl_correctRotation_Right( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter ); + break; + case LABEL_ALIGN_TOP: + lcl_correctRotation_Top( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter ); + break; + case LABEL_ALIGN_BOTTOM: + lcl_correctRotation_Bottom( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize, bRotateAroundCenter ); + break; + case LABEL_ALIGN_LEFT_TOP: + lcl_correctRotation_Left_Top( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize ); + break; + case LABEL_ALIGN_LEFT_BOTTOM: + lcl_correctRotation_Left_Bottom( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize ); + break; + case LABEL_ALIGN_RIGHT_TOP: + lcl_correctRotation_Right_Top( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize ); + break; + case LABEL_ALIGN_RIGHT_BOTTOM: + lcl_correctRotation_Right_Bottom( fXCorrection, fYCorrection, fAnglePositiveDegree, aSize ); + break; + default: //LABEL_ALIGN_CENTER + break; + } + + xShape2DText->setPosition( awt::Point( + static_cast<sal_Int32>(aOldPos.X + fXCorrection ) + , static_cast<sal_Int32>(aOldPos.Y + fYCorrection ) ) ); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/Linear3DTransformation.cxx b/chart2/source/view/main/Linear3DTransformation.cxx new file mode 100644 index 000000000..c27ffa316 --- /dev/null +++ b/chart2/source/view/main/Linear3DTransformation.cxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <Linear3DTransformation.hxx> + +using namespace ::com::sun::star; + +using ::com::sun::star::uno::Sequence; + +namespace chart +{ + + Linear3DTransformation::Linear3DTransformation( const drawing::HomogenMatrix& rHomMatrix, bool bSwapXAndY ) + : m_Matrix(rHomMatrix) + , m_bSwapXAndY(bSwapXAndY) +{} + +Linear3DTransformation::~Linear3DTransformation() +{} + +// ____ XTransformation ____ +Sequence< double > SAL_CALL Linear3DTransformation::transform( + const Sequence< double >& rSourceValues ) +{ + double fX = rSourceValues[0]; + double fY = rSourceValues[1]; + double fZ = rSourceValues[2]; + if(m_bSwapXAndY) + std::swap(fX,fY); + Sequence< double > aNewVec(3); + double fZwi; + + fZwi = m_Matrix.Line1.Column1 * fX + + m_Matrix.Line1.Column2 * fY + + m_Matrix.Line1.Column3 * fZ + + m_Matrix.Line1.Column4; + aNewVec[0] = fZwi; + + fZwi = m_Matrix.Line2.Column1 * fX + + m_Matrix.Line2.Column2 * fY + + m_Matrix.Line2.Column3 * fZ + + m_Matrix.Line2.Column4; + aNewVec[1] = fZwi; + + fZwi = m_Matrix.Line3.Column1 * fX + + m_Matrix.Line3.Column2 * fY + + m_Matrix.Line3.Column3 * fZ + + m_Matrix.Line3.Column4; + aNewVec[2] = fZwi; + + fZwi = m_Matrix.Line4.Column1 * fX + + m_Matrix.Line4.Column2 * fY + + m_Matrix.Line4.Column3 * fZ + + m_Matrix.Line4.Column4; + if(fZwi != 1.0 && fZwi != 0.0) + { + aNewVec[0] /= fZwi; + aNewVec[1] /= fZwi; + aNewVec[2] /= fZwi; + } + return aNewVec; +} + +sal_Int32 SAL_CALL Linear3DTransformation::getSourceDimension() +{ + return 3; +} + +sal_Int32 SAL_CALL Linear3DTransformation::getTargetDimension() +{ + return 3; +} + +} // namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/PlotterBase.cxx b/chart2/source/view/main/PlotterBase.cxx new file mode 100644 index 000000000..6479668c6 --- /dev/null +++ b/chart2/source/view/main/PlotterBase.cxx @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <PlotterBase.hxx> +#include <PlottingPositionHelper.hxx> +#include <ShapeFactory.hxx> +#include <rtl/math.hxx> +#include <osl/diagnose.h> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +PlotterBase::PlotterBase( sal_Int32 nDimensionCount ) + : m_pShapeFactory(nullptr) + , m_aCID() + , m_nDimension(nDimensionCount) + , m_pPosHelper(nullptr) +{ +} + +void PlotterBase::initPlotter( const uno::Reference< drawing::XShapes >& xLogicTarget + , const uno::Reference< drawing::XShapes >& xFinalTarget + , const uno::Reference< lang::XMultiServiceFactory >& xShapeFactory + , const OUString& rCID ) +{ + OSL_PRECOND(xLogicTarget.is()&&xFinalTarget.is()&&xShapeFactory.is(),"no proper initialization parameters"); + //is only allowed to be called once + m_xLogicTarget = xLogicTarget; + m_xFinalTarget = xFinalTarget; + m_xShapeFactory = xShapeFactory; + m_pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + m_aCID = rCID; +} + +PlotterBase::~PlotterBase() +{ +} + +void PlotterBase::setScales( const std::vector< ExplicitScaleData >& rScales, bool bSwapXAndYAxis ) +{ + if (!m_pPosHelper) + return; + + OSL_PRECOND(m_nDimension<=static_cast<sal_Int32>(rScales.size()),"Dimension of Plotter does not fit two dimension of given scale sequence"); + m_pPosHelper->setScales( rScales, bSwapXAndYAxis ); +} + +void PlotterBase::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix) +{ + if (!m_pPosHelper) + return; + + OSL_PRECOND(m_nDimension==2,"Set this transformation only in case of 2D"); + if(m_nDimension!=2) + return; + m_pPosHelper->setTransformationSceneToScreen( rMatrix ); +} + +uno::Reference< drawing::XShapes > PlotterBase::createGroupShape( + const uno::Reference< drawing::XShapes >& xTarget + , const OUString& rName ) +{ + if(!m_xShapeFactory.is()) + return nullptr; + + if(m_nDimension==2) + { + //create and add to target + return m_pShapeFactory->createGroup2D( xTarget, rName ); + } + else + { + //create and added to target + return m_pShapeFactory->createGroup3D( xTarget, rName ); + } +} + +bool PlotterBase::isValidPosition( const drawing::Position3D& rPos ) +{ + if( std::isnan(rPos.PositionX) ) + return false; + if( std::isnan(rPos.PositionY) ) + return false; + if( std::isnan(rPos.PositionZ) ) + return false; + if( std::isinf(rPos.PositionX) ) + return false; + if( std::isinf(rPos.PositionY) ) + return false; + if( std::isinf(rPos.PositionZ) ) + return false; + return true; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/PlottingPositionHelper.cxx b/chart2/source/view/main/PlottingPositionHelper.cxx new file mode 100644 index 000000000..d26e9331b --- /dev/null +++ b/chart2/source/view/main/PlottingPositionHelper.cxx @@ -0,0 +1,692 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <PlottingPositionHelper.hxx> +#include <CommonConverters.hxx> +#include <Linear3DTransformation.hxx> +#include <VPolarTransformation.hxx> +#include <ShapeFactory.hxx> +#include <PropertyMapper.hxx> +#include <defines.hxx> + +#include <com/sun/star/chart/TimeUnit.hpp> +#include <com/sun/star/chart2/AxisType.hpp> +#include <com/sun/star/drawing/DoubleSequence.hpp> +#include <com/sun/star/drawing/Position3D.hpp> +#include <com/sun/star/drawing/XShapes.hpp> + +#include <rtl/math.hxx> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +PlottingPositionHelper::PlottingPositionHelper() + : m_aScales() + , m_aMatrixScreenToScene() + , m_bSwapXAndY( false ) + , m_nXResolution( 1000 ) + , m_nYResolution( 1000 ) + , m_nZResolution( 1000 ) + , m_bMaySkipPointsInRegressionCalculation( true ) + , m_bDateAxis(false) + , m_nTimeResolution( css::chart::TimeUnit::DAY ) + , m_aNullDate(30,12,1899) + , m_fScaledCategoryWidth(1.0) + , m_bAllowShiftXAxisPos(false) + , m_bAllowShiftZAxisPos(false) +{ +} +PlottingPositionHelper::PlottingPositionHelper( const PlottingPositionHelper& rSource ) + : m_aScales( rSource.m_aScales ) + , m_aMatrixScreenToScene( rSource.m_aMatrixScreenToScene ) + // m_xTransformationLogicToScene( nullptr ) //should be recalculated + , m_bSwapXAndY( rSource.m_bSwapXAndY ) + , m_nXResolution( rSource.m_nXResolution ) + , m_nYResolution( rSource.m_nYResolution ) + , m_nZResolution( rSource.m_nZResolution ) + , m_bMaySkipPointsInRegressionCalculation( rSource.m_bMaySkipPointsInRegressionCalculation ) + , m_bDateAxis( rSource.m_bDateAxis ) + , m_nTimeResolution( rSource.m_nTimeResolution ) + , m_aNullDate( rSource.m_aNullDate ) + , m_fScaledCategoryWidth( rSource.m_fScaledCategoryWidth ) + , m_bAllowShiftXAxisPos( rSource.m_bAllowShiftXAxisPos ) + , m_bAllowShiftZAxisPos( rSource.m_bAllowShiftZAxisPos ) +{ +} + +PlottingPositionHelper::~PlottingPositionHelper() +{ + +} + +std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::clone() const +{ + return std::make_unique<PlottingPositionHelper>(*this); +} + +std::unique_ptr<PlottingPositionHelper> PlottingPositionHelper::createSecondaryPosHelper( const ExplicitScaleData& rSecondaryScale ) +{ + auto pRet = clone(); + pRet->m_aScales[1]=rSecondaryScale; + return pRet; +} + +void PlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix) +{ + m_aMatrixScreenToScene = HomogenMatrixToB3DHomMatrix(rMatrix); + m_xTransformationLogicToScene = nullptr; +} + +void PlottingPositionHelper::setScales( const std::vector< ExplicitScaleData >& rScales, bool bSwapXAndYAxis ) +{ + m_aScales = rScales; + m_bSwapXAndY = bSwapXAndYAxis; + m_xTransformationLogicToScene = nullptr; +} + +uno::Reference< XTransformation > PlottingPositionHelper::getTransformationScaledLogicToScene() const +{ + //this is a standard transformation for a cartesian coordinate system + + //transformation from 2) to 4) //@todo 2) and 4) need an ink to a document + + //we need to apply this transformation to each geometric object because of a bug/problem + //of the old drawing layer (the UNO_NAME_3D_EXTRUDE_DEPTH is an integer value instead of a double ) + if(!m_xTransformationLogicToScene.is()) + { + ::basegfx::B3DHomMatrix aMatrix; + double MinX = getLogicMinX(); + double MinY = getLogicMinY(); + double MinZ = getLogicMinZ(); + double MaxX = getLogicMaxX(); + double MaxY = getLogicMaxY(); + double MaxZ = getLogicMaxZ(); + + AxisOrientation nXAxisOrientation = m_aScales[0].Orientation; + AxisOrientation nYAxisOrientation = m_aScales[1].Orientation; + AxisOrientation nZAxisOrientation = m_aScales[2].Orientation; + + //apply scaling + doUnshiftedLogicScaling( &MinX, &MinY, &MinZ ); + doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ); + + if(m_bSwapXAndY) + { + std::swap(MinX,MinY); + std::swap(MaxX,MaxY); + std::swap(nXAxisOrientation,nYAxisOrientation); + } + + double fWidthX = MaxX - MinX; + double fWidthY = MaxY - MinY; + double fWidthZ = MaxZ - MinZ; + + double fScaleDirectionX = nXAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0; + double fScaleDirectionY = nYAxisOrientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0; + double fScaleDirectionZ = nZAxisOrientation==AxisOrientation_MATHEMATICAL ? -1.0 : 1.0; + + double fScaleX = fScaleDirectionX*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthX; + double fScaleY = fScaleDirectionY*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthY; + double fScaleZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ; + + aMatrix.scale(fScaleX, fScaleY, fScaleZ); + + if( nXAxisOrientation==AxisOrientation_MATHEMATICAL ) + aMatrix.translate(-MinX*fScaleX, 0.0, 0.0); + else + aMatrix.translate(-MaxX*fScaleX, 0.0, 0.0); + if( nYAxisOrientation==AxisOrientation_MATHEMATICAL ) + aMatrix.translate(0.0, -MinY*fScaleY, 0.0); + else + aMatrix.translate(0.0, -MaxY*fScaleY, 0.0); + if( nZAxisOrientation==AxisOrientation_MATHEMATICAL ) + aMatrix.translate(0.0, 0.0, -MaxZ*fScaleZ);//z direction in draw is reverse mathematical direction + else + aMatrix.translate(0.0, 0.0, -MinZ*fScaleZ); + + aMatrix = m_aMatrixScreenToScene*aMatrix; + + m_xTransformationLogicToScene = new Linear3DTransformation(B3DHomMatrixToHomogenMatrix( aMatrix ),m_bSwapXAndY); + } + return m_xTransformationLogicToScene; +} + +drawing::Position3D PlottingPositionHelper::transformLogicToScene( + double fX, double fY, double fZ, bool bClip ) const +{ + doLogicScaling( &fX,&fY,&fZ ); + if(bClip) + clipScaledLogicValues( &fX,&fY,&fZ ); + + return transformScaledLogicToScene( fX, fY, fZ, false ); +} + +drawing::Position3D PlottingPositionHelper::transformScaledLogicToScene( + double fX, double fY, double fZ, bool bClip ) const +{ + if( bClip ) + clipScaledLogicValues( &fX,&fY,&fZ ); + + drawing::Position3D aPos( fX, fY, fZ); + + uno::Reference< XTransformation > xTransformation = + getTransformationScaledLogicToScene(); + uno::Sequence< double > aSeq = + xTransformation->transform( Position3DToSequence(aPos) ); + return SequenceToPosition3D(aSeq); +} + +awt::Point PlottingPositionHelper::transformSceneToScreenPosition( const drawing::Position3D& rScenePosition3D + , const uno::Reference< drawing::XShapes >& xSceneTarget + , ShapeFactory* pShapeFactory + , sal_Int32 nDimensionCount ) +{ + //@todo would like to have a cheaper method to do this transformation + awt::Point aScreenPoint( static_cast<sal_Int32>(rScenePosition3D.PositionX), static_cast<sal_Int32>(rScenePosition3D.PositionY) ); + + //transformation from scene to screen (only necessary for 3D): + if(nDimensionCount==3) + { + //create 3D anchor shape + tPropertyNameMap aDummyPropertyNameMap; + uno::Reference< drawing::XShape > xShape3DAnchor = pShapeFactory->createCube( xSceneTarget + , rScenePosition3D,drawing::Direction3D(1,1,1) + , 0, nullptr, aDummyPropertyNameMap); + //get 2D position from xShape3DAnchor + aScreenPoint = xShape3DAnchor->getPosition(); + xSceneTarget->remove(xShape3DAnchor); + } + return aScreenPoint; +} + +void PlottingPositionHelper::transformScaledLogicToScene( drawing::PolyPolygonShape3D& rPolygon ) const +{ + drawing::Position3D aScenePosition; + for( sal_Int32 nS = rPolygon.SequenceX.getLength(); nS--;) + { + drawing::DoubleSequence& xValues = rPolygon.SequenceX[nS]; + drawing::DoubleSequence& yValues = rPolygon.SequenceY[nS]; + drawing::DoubleSequence& zValues = rPolygon.SequenceZ[nS]; + for( sal_Int32 nP = xValues.getLength(); nP--; ) + { + double& fX = xValues[nP]; + double& fY = yValues[nP]; + double& fZ = zValues[nP]; + aScenePosition = transformScaledLogicToScene( fX,fY,fZ,true ); + fX = aScenePosition.PositionX; + fY = aScenePosition.PositionY; + fZ = aScenePosition.PositionZ; + } + } +} + +void PlottingPositionHelper::clipScaledLogicValues( double* pX, double* pY, double* pZ ) const +{ + //get logic clip values: + double MinX = getLogicMinX(); + double MinY = getLogicMinY(); + double MinZ = getLogicMinZ(); + double MaxX = getLogicMaxX(); + double MaxY = getLogicMaxY(); + double MaxZ = getLogicMaxZ(); + + //apply scaling + doUnshiftedLogicScaling( &MinX, &MinY, &MinZ ); + doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ); + + if(pX) + { + if( *pX < MinX ) + *pX = MinX; + else if( *pX > MaxX ) + *pX = MaxX; + } + if(pY) + { + if( *pY < MinY ) + *pY = MinY; + else if( *pY > MaxY ) + *pY = MaxY; + } + if(pZ) + { + if( *pZ < MinZ ) + *pZ = MinZ; + else if( *pZ > MaxZ ) + *pZ = MaxZ; + } +} + +basegfx::B2DRectangle PlottingPositionHelper::getScaledLogicClipDoubleRect() const +{ + //get logic clip values: + double MinX = getLogicMinX(); + double MinY = getLogicMinY(); + double MinZ = getLogicMinZ(); + double MaxX = getLogicMaxX(); + double MaxY = getLogicMaxY(); + double MaxZ = getLogicMaxZ(); + + //apply scaling + doUnshiftedLogicScaling( &MinX, &MinY, &MinZ ); + doUnshiftedLogicScaling( &MaxX, &MaxY, &MaxZ); + + basegfx::B2DRectangle aRet( MinX, MaxY, MaxX, MinY ); + return aRet; +} + +drawing::Direction3D PlottingPositionHelper::getScaledLogicWidth() const +{ + drawing::Direction3D aRet; + + double MinX = getLogicMinX(); + double MinY = getLogicMinY(); + double MinZ = getLogicMinZ(); + double MaxX = getLogicMaxX(); + double MaxY = getLogicMaxY(); + double MaxZ = getLogicMaxZ(); + + doLogicScaling( &MinX, &MinY, &MinZ ); + doLogicScaling( &MaxX, &MaxY, &MaxZ); + + aRet.DirectionX = MaxX - MinX; + aRet.DirectionY = MaxY - MinY; + aRet.DirectionZ = MaxZ - MinZ; + return aRet; +} + +PolarPlottingPositionHelper::PolarPlottingPositionHelper() + : m_fRadiusOffset(0.0) + , m_fAngleDegreeOffset(90.0) + , m_aUnitCartesianToScene() +{ + m_bMaySkipPointsInRegressionCalculation = false; +} + +PolarPlottingPositionHelper::PolarPlottingPositionHelper( const PolarPlottingPositionHelper& rSource ) + : PlottingPositionHelper(rSource) + , m_fRadiusOffset( rSource.m_fRadiusOffset ) + , m_fAngleDegreeOffset( rSource.m_fAngleDegreeOffset ) + , m_aUnitCartesianToScene( rSource.m_aUnitCartesianToScene ) +{ +} + +PolarPlottingPositionHelper::~PolarPlottingPositionHelper() +{ +} + +std::unique_ptr<PlottingPositionHelper> PolarPlottingPositionHelper::clone() const +{ + return std::make_unique<PolarPlottingPositionHelper>(*this); +} + +void PolarPlottingPositionHelper::setTransformationSceneToScreen( const drawing::HomogenMatrix& rMatrix) +{ + PlottingPositionHelper::setTransformationSceneToScreen( rMatrix); + m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene ); +} +void PolarPlottingPositionHelper::setScales( const std::vector< ExplicitScaleData >& rScales, bool bSwapXAndYAxis ) +{ + PlottingPositionHelper::setScales( rScales, bSwapXAndYAxis ); + m_aUnitCartesianToScene =impl_calculateMatrixUnitCartesianToScene( m_aMatrixScreenToScene ); +} + +::basegfx::B3DHomMatrix PolarPlottingPositionHelper::impl_calculateMatrixUnitCartesianToScene( const ::basegfx::B3DHomMatrix& rMatrixScreenToScene ) const +{ + ::basegfx::B3DHomMatrix aRet; + + if( m_aScales.empty() ) + return aRet; + + double fTranslate =1.0; + double fScale =FIXED_SIZE_FOR_3D_CHART_VOLUME/2.0; + + double fTranslateLogicZ; + double fScaleLogicZ; + { + double fScaleDirectionZ = m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL ? 1.0 : -1.0; + double MinZ = getLogicMinZ(); + double MaxZ = getLogicMaxZ(); + doLogicScaling( nullptr, nullptr, &MinZ ); + doLogicScaling( nullptr, nullptr, &MaxZ ); + double fWidthZ = MaxZ - MinZ; + + if( m_aScales[2].Orientation==AxisOrientation_MATHEMATICAL ) + fTranslateLogicZ=MinZ; + else + fTranslateLogicZ=MaxZ; + fScaleLogicZ = fScaleDirectionZ*FIXED_SIZE_FOR_3D_CHART_VOLUME/fWidthZ; + } + + double fTranslateX = fTranslate; + double fTranslateY = fTranslate; + double fTranslateZ = fTranslateLogicZ; + + double fScaleX = fScale; + double fScaleY = fScale; + double fScaleZ = fScaleLogicZ; + + aRet.translate(fTranslateX, fTranslateY, fTranslateZ);//x first + aRet.scale(fScaleX, fScaleY, fScaleZ);//x first + + aRet = rMatrixScreenToScene * aRet; + return aRet; +} + +uno::Reference< XTransformation > PolarPlottingPositionHelper::getTransformationScaledLogicToScene() const +{ + if( !m_xTransformationLogicToScene.is() ) + m_xTransformationLogicToScene = new VPolarTransformation(*this); + return m_xTransformationLogicToScene; +} + +double PolarPlottingPositionHelper::getWidthAngleDegree( double& fStartLogicValueOnAngleAxis, double& fEndLogicValueOnAngleAxis ) const +{ + const ExplicitScaleData& rAngleScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0]; + if( rAngleScale.Orientation != AxisOrientation_MATHEMATICAL ) + { + double fHelp = fEndLogicValueOnAngleAxis; + fEndLogicValueOnAngleAxis = fStartLogicValueOnAngleAxis; + fStartLogicValueOnAngleAxis = fHelp; + } + + double fStartAngleDegree = transformToAngleDegree( fStartLogicValueOnAngleAxis ); + double fEndAngleDegree = transformToAngleDegree( fEndLogicValueOnAngleAxis ); + double fWidthAngleDegree = fEndAngleDegree - fStartAngleDegree; + + if( ::rtl::math::approxEqual( fStartAngleDegree, fEndAngleDegree ) + && !::rtl::math::approxEqual( fStartLogicValueOnAngleAxis, fEndLogicValueOnAngleAxis ) ) + fWidthAngleDegree = 360.0; + + // tdf#123504: both 0 and 360 are valid and different values here! + while (fWidthAngleDegree < 0.0) + fWidthAngleDegree += 360.0; + while (fWidthAngleDegree > 360.0) + fWidthAngleDegree -= 360.0; + + return fWidthAngleDegree; +} + +//This method does a lot of computation for understanding which scale to +//utilize and if reverse orientation should be used. Indeed, for a pie or donut, +//the final result is as simple as multiplying by 360 and adding +//`m_fAngleDegreeOffset`. +double PolarPlottingPositionHelper::transformToAngleDegree( double fLogicValueOnAngleAxis, bool bDoScaling ) const +{ + double fRet=0.0; + + double fAxisAngleScaleDirection = 1.0; + { + const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[1] : m_aScales[0]; + if(rScale.Orientation != AxisOrientation_MATHEMATICAL) + fAxisAngleScaleDirection *= -1.0; + } + + double MinAngleValue = 0.0; + double MaxAngleValue = 0.0; + { + double MinX = getLogicMinX(); + double MinY = getLogicMinY(); + double MaxX = getLogicMaxX(); + double MaxY = getLogicMaxY(); + double MinZ = getLogicMinZ(); + double MaxZ = getLogicMaxZ(); + + doLogicScaling( &MinX, &MinY, &MinZ ); + doLogicScaling( &MaxX, &MaxY, &MaxZ); + + MinAngleValue = m_bSwapXAndY ? MinY : MinX; + MaxAngleValue = m_bSwapXAndY ? MaxY : MaxX; + } + + double fScaledLogicAngleValue = 0.0; + if(bDoScaling) + { + double fX = m_bSwapXAndY ? getLogicMaxX() : fLogicValueOnAngleAxis; + double fY = m_bSwapXAndY ? fLogicValueOnAngleAxis : getLogicMaxY(); + double fZ = getLogicMaxZ(); + clipLogicValues( &fX, &fY, &fZ ); + doLogicScaling( &fX, &fY, &fZ ); + fScaledLogicAngleValue = m_bSwapXAndY ? fY : fX; + } + else + fScaledLogicAngleValue = fLogicValueOnAngleAxis; + + fRet = m_fAngleDegreeOffset + + fAxisAngleScaleDirection*(fScaledLogicAngleValue-MinAngleValue)*360.0 + /fabs(MaxAngleValue-MinAngleValue); + // tdf#123504: both 0 and 360 are valid and different values here! + while (fRet > 360.0) + fRet -= 360.0; + while (fRet < 0) + fRet += 360.0; + return fRet; +} + +/** + * Given a value in the radius axis scale range, it returns, in the simplest + * case (that is when `m_fRadiusOffset` is zero), the normalized value; when + * `m_fRadiusOffset` is not zero (e.g. as in the case of a donut), the interval + * used for normalization is extended by `m_fRadiusOffset`: if the axis + * orientation is not reversed the new interval becomes + * [scale.Minimum - m_fRadiusOffset, scale.Maximum] else it becomes + * [scale.Minimum, scale.Maximum + m_fRadiusOffset]. + * Pay attention here! For the latter case, since the axis orientation is + * reversed, the normalization is reversed too. Indeed, we have + * `transformToRadius(scale.Maximum + m_fRadiusOffset) = 0` and + * `transformToRadius(scale.Minimum) = 1`. + * + * For a pie chart the radius axis scale range is initialized by the + * `getMinimum` and `getMaximum` methods of the `PieChart` object (see notes + * for `VCoordinateSystem::prepareAutomaticAxisScaling`). + * So we have scale.Minimum = 0.5 (always constant!) and + * scale.Maximum = 0.5 + number_of_rings + max_offset + * (see notes for `PieChart::getMaxOffset`). + * Hence we get the following general formulas for computing normalized inner + * and outer radius: + * + * 1- transformToRadius(inner_radius) = + * (number_of_rings - (ring_index + 1) + m_fRadiusOffset) + * / (number_of_rings + max_offset + m_fRadiusOffset) + * + * 2- transformToRadius(outer_radius) = + * (1 + number_of_rings - (ring_index + 1) + m_fRadiusOffset) + * / (number_of_rings + max_offset + m_fRadiusOffset). + * + * Here you have to take into account that values for inner and outer radius + * are swapped since the radius axis is reversed (See notes for + * `PiePositionHelper::getInnerAndOuterRadius`). So indeed inner_radius is + * the outer and outer_radius is the inner. Anyway still because of the reverse + * orientation, the normalization performed by `transformToRadius` is reversed + * too, as we have seen above. Hence `transformToRadius(inner_radius)` is + * really the normalized inner radius and `transformToRadius(outer_radius)` is + * really the normalized outer radius. + * + * Some basic examples where we apply the above formulas: + * 1- For a non-exploded pie chart we have: + * `transformToRadius(inner_radius) = 0`, + * `transformToRadius(outer_radius) = 1`. + * 2- For a non-exploded donut with a single ring we have: + * `transformToRadius(inner_radius) = + * m_fRadiusOffset/(1 + m_fRadiusOffset)`, + * `transformToRadius(outer_radius) = + * (1 + m_fRadiusOffset)/(1 + m_fRadiusOffset) = 1`. + * 3- For an exploded pie chart we have: + * `transformToRadius(inner_radius) = 0/(1 + max_offset) = 0`, + * `transformToRadius(outer_radius) = 1/(1 + max_offset)`. + * + * The third example needs some remark. Both the logical inner and outer + * radius passed to `transformToRadius` are offset by `max_offset`. + * However the returned normalized values do not contain any (normalized) + * offset term at all, otherwise the returned values would be + * `max_offset/(1 + max_offset)` and `1`. Hence, for exploded pie/donut, + * `transformToRadius` returns the normalized value of radii without any + * offset term. These values are smaller than in the non-exploded case by an + * amount equals to the value of the normalized maximum offset + * (`max_offset/(1 + max_offset)` in the example above). That is due to the + * fact that the normalization keeps into account the space needed for the + * offset. This is the correct behavior, in fact the offset for the current + * slice could be different from the maximum offset. + * These remarks should clarify why the `PieChart::createDataPoint` and + * `PieChart::createTextLabelShape` methods add the normalized offset (for the + * current slice) to the normalized radii in order to achieve the correct + * placement of slice and text shapes. + */ +double PolarPlottingPositionHelper::transformToRadius( double fLogicValueOnRadiusAxis, bool bDoScaling ) const +{ + double fNormalRadius = 0.0; + { + double fScaledLogicRadiusValue = 0.0; + double fX = m_bSwapXAndY ? fLogicValueOnRadiusAxis: getLogicMaxX(); + double fY = m_bSwapXAndY ? getLogicMaxY() : fLogicValueOnRadiusAxis; + if(bDoScaling) + doLogicScaling( &fX, &fY, nullptr ); + + fScaledLogicRadiusValue = m_bSwapXAndY ? fX : fY; + + bool bMinIsInnerRadius = true; + const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1]; + if(rScale.Orientation != AxisOrientation_MATHEMATICAL) + bMinIsInnerRadius = false; + + double fInnerScaledLogicRadius=0.0; + double fOuterScaledLogicRadius=0.0; + { + double MinX = getLogicMinX(); + double MinY = getLogicMinY(); + doLogicScaling( &MinX, &MinY, nullptr ); + double MaxX = getLogicMaxX(); + double MaxY = getLogicMaxY(); + doLogicScaling( &MaxX, &MaxY, nullptr ); + + double fMin = m_bSwapXAndY ? MinX : MinY; + double fMax = m_bSwapXAndY ? MaxX : MaxY; + + fInnerScaledLogicRadius = bMinIsInnerRadius ? fMin : fMax; + fOuterScaledLogicRadius = bMinIsInnerRadius ? fMax : fMin; + } + + if( bMinIsInnerRadius ) + fInnerScaledLogicRadius -= fabs(m_fRadiusOffset); + else + fInnerScaledLogicRadius += fabs(m_fRadiusOffset); + fNormalRadius = (fScaledLogicRadiusValue-fInnerScaledLogicRadius)/(fOuterScaledLogicRadius-fInnerScaledLogicRadius); + } + return fNormalRadius; +} + +drawing::Position3D PolarPlottingPositionHelper::transformLogicToScene( double fX, double fY, double fZ, bool bClip ) const +{ + if(bClip) + clipLogicValues( &fX,&fY,&fZ ); + double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX; + double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY; + return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ ); +} + +drawing::Position3D PolarPlottingPositionHelper::transformScaledLogicToScene( double fX, double fY, double fZ, bool bClip ) const +{ + if(bClip) + clipScaledLogicValues( &fX,&fY,&fZ ); + double fLogicValueOnAngleAxis = m_bSwapXAndY ? fY : fX; + double fLogicValueOnRadiusAxis = m_bSwapXAndY ? fX : fY; + return transformAngleRadiusToScene( fLogicValueOnAngleAxis, fLogicValueOnRadiusAxis, fZ, false ); +} +drawing::Position3D PolarPlottingPositionHelper::transformUnitCircleToScene( double fUnitAngleDegree, double fUnitRadius + , double fLogicZ ) const +{ + double fAnglePi = basegfx::deg2rad(fUnitAngleDegree); + + double fX=fUnitRadius*rtl::math::cos(fAnglePi); + double fY=fUnitRadius*rtl::math::sin(fAnglePi); + double fZ=fLogicZ; + + //!! applying matrix to vector does ignore translation, so it is important to use a B3DPoint here instead of B3DVector + ::basegfx::B3DPoint aPoint(fX,fY,fZ); + ::basegfx::B3DPoint aRet = m_aUnitCartesianToScene * aPoint; + return B3DPointToPosition3D(aRet); +} + +drawing::Position3D PolarPlottingPositionHelper::transformAngleRadiusToScene( double fLogicValueOnAngleAxis, double fLogicValueOnRadiusAxis, double fLogicZ, bool bDoScaling ) const +{ + double fUnitAngleDegree = transformToAngleDegree(fLogicValueOnAngleAxis,bDoScaling); + double fUnitRadius = transformToRadius(fLogicValueOnRadiusAxis,bDoScaling); + + return transformUnitCircleToScene( fUnitAngleDegree, fUnitRadius, fLogicZ ); +} + +double PolarPlottingPositionHelper::getOuterLogicRadius() const +{ + const ExplicitScaleData& rScale = m_bSwapXAndY ? m_aScales[0] : m_aScales[1]; + if( rScale.Orientation==AxisOrientation_MATHEMATICAL ) + return rScale.Maximum; + else + return rScale.Minimum; +} + +bool PlottingPositionHelper::isPercentY() const +{ + return m_aScales[1].AxisType==AxisType::PERCENT; +} + +double PlottingPositionHelper::getBaseValueY() const +{ + return m_aScales[1].Origin; +} + +void PlottingPositionHelper::setTimeResolution( long nTimeResolution, const Date& rNullDate ) +{ + m_nTimeResolution = nTimeResolution; + m_aNullDate = rNullDate; + + //adapt category width + double fCategoryWidth = 1.0; + if( !m_aScales.empty() ) + { + if( m_aScales[0].AxisType == css::chart2::AxisType::DATE ) + { + m_bDateAxis = true; + if( nTimeResolution == css::chart::TimeUnit::YEAR ) + { + const double fMonthCount = 12.0;//todo: this depends on the DateScaling and must be adjusted in case we use more generic calendars in future + fCategoryWidth = fMonthCount; + } + } + } + setScaledCategoryWidth(fCategoryWidth); +} + +void PlottingPositionHelper::setScaledCategoryWidth( double fScaledCategoryWidth ) +{ + m_fScaledCategoryWidth = fScaledCategoryWidth; +} +void PlottingPositionHelper::AllowShiftXAxisPos( bool bAllowShift ) +{ + m_bAllowShiftXAxisPos = bAllowShift; +} +void PlottingPositionHelper::AllowShiftZAxisPos( bool bAllowShift ) +{ + m_bAllowShiftZAxisPos = bAllowShift; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/PolarLabelPositionHelper.cxx b/chart2/source/view/main/PolarLabelPositionHelper.cxx new file mode 100644 index 000000000..a3e9a7cd9 --- /dev/null +++ b/chart2/source/view/main/PolarLabelPositionHelper.cxx @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <PolarLabelPositionHelper.hxx> +#include <PlottingPositionHelper.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <basegfx/vector/b2ivector.hxx> + +#include <com/sun/star/chart/DataLabelPlacement.hpp> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +PolarLabelPositionHelper::PolarLabelPositionHelper( + PolarPlottingPositionHelper* pPosHelper + , sal_Int32 nDimensionCount + , const uno::Reference< drawing::XShapes >& xLogicTarget + , ShapeFactory* pShapeFactory ) + : LabelPositionHelper( nDimensionCount, xLogicTarget, pShapeFactory ) + , m_pPosHelper(pPosHelper) +{ +} + +PolarLabelPositionHelper::~PolarLabelPositionHelper() +{ +} + +awt::Point PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForLogicValues( + LabelAlignment& rAlignment + , double fLogicValueOnAngleAxis + , double fLogicValueOnRadiusAxis + , double fLogicZ + , sal_Int32 nScreenValueOffsetInRadiusDirection ) const +{ + double fUnitCircleAngleDegree = m_pPosHelper->transformToAngleDegree( fLogicValueOnAngleAxis ); + double fUnitCircleRadius = m_pPosHelper->transformToRadius( fLogicValueOnRadiusAxis ); + + return getLabelScreenPositionAndAlignmentForUnitCircleValues( + rAlignment, css::chart::DataLabelPlacement::OUTSIDE + , fUnitCircleAngleDegree, 0.0 + , fUnitCircleRadius, fUnitCircleRadius, fLogicZ, nScreenValueOffsetInRadiusDirection ); +} + +awt::Point PolarLabelPositionHelper::getLabelScreenPositionAndAlignmentForUnitCircleValues( + LabelAlignment& rAlignment, sal_Int32 nLabelPlacement + , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree + , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius + , double fLogicZ + , sal_Int32 nScreenValueOffsetInRadiusDirection ) const +{ + bool bCenter = (nLabelPlacement != css::chart::DataLabelPlacement::OUTSIDE) + && (nLabelPlacement != css::chart::DataLabelPlacement::INSIDE); + + double fAngleDegree = fUnitCircleStartAngleDegree + fUnitCircleWidthAngleDegree/2.0; + double fRadius = 0.0; + if( !bCenter ) //e.g. for pure pie chart(one ring only) or for angle axis of polar coordinate system + fRadius = fUnitCircleOuterRadius; + else + fRadius = fUnitCircleInnerRadius + (fUnitCircleOuterRadius-fUnitCircleInnerRadius)/2.0 ; + + awt::Point aRet( transformSceneToScreenPosition( + m_pPosHelper->transformUnitCircleToScene( fAngleDegree, fRadius, fLogicZ+0.5 ) ) ); + + if(m_nDimensionCount==3 && nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE) + { + //check whether the upper or the downer edge is more distant from the center + //take the farthest point to put the label to + + awt::Point aP0( transformSceneToScreenPosition( + m_pPosHelper->transformUnitCircleToScene( 0, 0, fLogicZ ) ) ); + awt::Point aP1(aRet); + awt::Point aP2( transformSceneToScreenPosition( + m_pPosHelper->transformUnitCircleToScene( fAngleDegree, fRadius, fLogicZ-0.5 ) ) ); + + ::basegfx::B2DVector aV0( aP0.X, aP0.Y ); + ::basegfx::B2DVector aV1( aP1.X, aP1.Y ); + ::basegfx::B2DVector aV2( aP2.X, aP2.Y ); + + double fL1 = ::basegfx::B2DVector(aV1-aV0).getLength(); + double fL2 = ::basegfx::B2DVector(aV2-aV0).getLength(); + + if(fL2>fL1) + aRet = aP2; + + //calculate new angle for alignment + double fDX = aRet.X-aP0.X; + double fDY = aRet.Y-aP0.Y; + fDY*=-1.0;//drawing layer has inverse y values + if( fDX != 0.0 ) + { + fAngleDegree = basegfx::rad2deg(atan(fDY/fDX)); + if(fDX<0.0) + fAngleDegree+=180.0; + } + else + { + if(fDY>0.0) + fAngleDegree = 90.0; + else + fAngleDegree = 270.0; + } + } + //set LabelAlignment + if( !bCenter ) + { + // tdf#123504: both 0 and 360 are valid and different values here! + while (fAngleDegree > 360.0) + fAngleDegree -= 360.0; + while (fAngleDegree < 0.0) + fAngleDegree += 360.0; + + bool bOutside = nLabelPlacement == css::chart::DataLabelPlacement::OUTSIDE; + + if(fAngleDegree==0.0) + rAlignment = LABEL_ALIGN_CENTER; + else if(fAngleDegree<=22.5) + rAlignment = bOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT; + else if(fAngleDegree<67.5) + rAlignment = bOutside ? LABEL_ALIGN_RIGHT_TOP : LABEL_ALIGN_LEFT_BOTTOM; + else if(fAngleDegree<112.5) + rAlignment = bOutside ? LABEL_ALIGN_TOP : LABEL_ALIGN_BOTTOM; + else if(fAngleDegree<=157.5) + rAlignment = bOutside ? LABEL_ALIGN_LEFT_TOP : LABEL_ALIGN_RIGHT_BOTTOM; + else if(fAngleDegree<=202.5) + rAlignment = bOutside ? LABEL_ALIGN_LEFT : LABEL_ALIGN_RIGHT; + else if(fAngleDegree<247.5) + rAlignment = bOutside ? LABEL_ALIGN_LEFT_BOTTOM : LABEL_ALIGN_RIGHT_TOP; + else if(fAngleDegree<292.5) + rAlignment = bOutside ? LABEL_ALIGN_BOTTOM : LABEL_ALIGN_TOP; + else if(fAngleDegree<337.5) + rAlignment = bOutside ? LABEL_ALIGN_RIGHT_BOTTOM : LABEL_ALIGN_LEFT_TOP; + else + rAlignment = bOutside ? LABEL_ALIGN_RIGHT : LABEL_ALIGN_LEFT; + } + else + { + rAlignment = LABEL_ALIGN_CENTER; + } + + //add a scaling independent Offset if requested + if( nScreenValueOffsetInRadiusDirection != 0) + { + awt::Point aOrigin( transformSceneToScreenPosition( + m_pPosHelper->transformUnitCircleToScene( 0.0, 0.0, fLogicZ+0.5 ) ) ); + basegfx::B2IVector aDirection( aRet.X- aOrigin.X, aRet.Y- aOrigin.Y ); + aDirection.setLength(nScreenValueOffsetInRadiusDirection); + aRet.X += aDirection.getX(); + aRet.Y += aDirection.getY(); + } + + return aRet; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/PropertyMapper.cxx b/chart2/source/view/main/PropertyMapper.cxx new file mode 100644 index 000000000..b33ab3ee3 --- /dev/null +++ b/chart2/source/view/main/PropertyMapper.cxx @@ -0,0 +1,511 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <PropertyMapper.hxx> +#include <unonames.hxx> + +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/LineJoint.hpp> +#include <comphelper/sequence.hxx> +#include <tools/diagnose_ex.h> + +namespace chart +{ +using namespace ::com::sun::star; + +namespace +{ + +void lcl_overwriteOrAppendValues( + tPropertyNameValueMap &rMap, const tPropertyNameValueMap& rOverwriteMap ) +{ + for (auto const& elem : rOverwriteMap) + rMap[ elem.first ] = elem.second; +} + +} // anonymous namespace + +void PropertyMapper::setMappedProperties( + const uno::Reference< beans::XPropertySet >& xTarget + , const uno::Reference< beans::XPropertySet >& xSource + , const tPropertyNameMap& rMap + , tPropertyNameValueMap const * pOverwriteMap ) +{ + if( !xTarget.is() || !xSource.is() ) + return; + + tNameSequence aNames; + tAnySequence aValues; + getMultiPropertyLists(aNames, aValues, xSource, rMap ); + if(pOverwriteMap && (aNames.getLength() == aValues.getLength())) + { + tPropertyNameValueMap aNewMap; + for( sal_Int32 nI=0; nI<aNames.getLength(); ++nI ) + aNewMap[ aNames[nI] ] = aValues[nI]; + lcl_overwriteOrAppendValues( aNewMap, *pOverwriteMap ); + aNames = comphelper::mapKeysToSequence( aNewMap ); + aValues = comphelper::mapValuesToSequence( aNewMap ); + } + + PropertyMapper::setMultiProperties( aNames, aValues, xTarget ); +} + +void PropertyMapper::getValueMap( + tPropertyNameValueMap& rValueMap + , const tPropertyNameMap& rNameMap + , const uno::Reference< beans::XPropertySet >& xSourceProp + ) +{ + uno::Reference< beans::XMultiPropertySet > xMultiPropSet(xSourceProp, uno::UNO_QUERY); + if((false) && xMultiPropSet.is()) + { + uno::Sequence< OUString > aPropSourceNames(rNameMap.size()); + uno::Sequence< OUString > aPropTargetNames(rNameMap.size()); + sal_Int32 i = 0; + for (auto const& elem : rNameMap) + { + aPropTargetNames[i] = elem.first; + aPropSourceNames[i] = elem.second; + ++i; + } + + uno::Sequence< uno::Any > xValues = xMultiPropSet->getPropertyValues(aPropSourceNames); + sal_Int32 n = rNameMap.size(); + for(i = 0;i < n; ++i) + { + if( xValues[i].hasValue() ) + rValueMap.emplace( aPropTargetNames[i], xValues[i] ); + } + } + else + { + for (auto const& elem : rNameMap) + { + OUString aTarget = elem.first; + OUString aSource = elem.second; + try + { + uno::Any aAny( xSourceProp->getPropertyValue(aSource) ); + if( aAny.hasValue() ) + rValueMap.emplace( aTarget, aAny ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } +} + +void PropertyMapper::getMultiPropertyLists( + tNameSequence& rNames + , tAnySequence& rValues + , const uno::Reference< beans::XPropertySet >& xSourceProp + , const tPropertyNameMap& rNameMap + ) +{ + tPropertyNameValueMap aValueMap; + getValueMap( aValueMap, rNameMap, xSourceProp ); + getMultiPropertyListsFromValueMap( rNames, rValues, aValueMap ); +} + +void PropertyMapper::getMultiPropertyListsFromValueMap( + tNameSequence& rNames + , tAnySequence& rValues + , const tPropertyNameValueMap& rValueMap + ) +{ + sal_Int32 nPropertyCount = rValueMap.size(); + rNames.realloc(nPropertyCount); + rValues.realloc(nPropertyCount); + + //fill sequences + sal_Int32 nN=0; + for (auto const& elem : rValueMap) + { + const uno::Any& rAny = elem.second; + if( rAny.hasValue() ) + { + //do not set empty anys because of performance (otherwise SdrAttrObj::ItemChange will take much longer) + rNames[nN] = elem.first; + rValues[nN] = rAny; + ++nN; + } + } + //reduce to real property count + rNames.realloc(nN); + rValues.realloc(nN); +} + +uno::Any* PropertyMapper::getValuePointer( tAnySequence& rPropValues + , const tNameSequence& rPropNames + , const OUString& rPropName ) +{ + sal_Int32 nCount = rPropNames.getLength(); + for( sal_Int32 nN = 0; nN < nCount; nN++ ) + { + if(rPropNames[nN] == rPropName) + return &rPropValues[nN]; + } + return nullptr; +} + +uno::Any* PropertyMapper::getValuePointerForLimitedSpace( tAnySequence& rPropValues + , const tNameSequence& rPropNames + , bool bLimitedHeight) +{ + return PropertyMapper::getValuePointer( rPropValues, rPropNames + , bLimitedHeight ? OUString("TextMaximumFrameHeight") : OUString("TextMaximumFrameWidth") ); +} + +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForCharacterProperties() +{ + //shape property -- chart model object property + static tPropertyNameMap s_aShapePropertyMapForCharacterProperties{ + {"CharColor", "CharColor"}, + {"CharContoured", "CharContoured"}, + {"CharEmphasis", "CharEmphasis"},//the service style::CharacterProperties describes a property called 'CharEmphasize' which is nowhere implemented + + {"CharFontFamily", "CharFontFamily"}, + {"CharFontFamilyAsian", "CharFontFamilyAsian"}, + {"CharFontFamilyComplex", "CharFontFamilyComplex"}, + {"CharFontCharSet", "CharFontCharSet"}, + {"CharFontCharSetAsian", "CharFontCharSetAsian"}, + {"CharFontCharSetComplex", "CharFontCharSetComplex"}, + {"CharFontName", "CharFontName"}, + {"CharFontNameAsian", "CharFontNameAsian"}, + {"CharFontNameComplex", "CharFontNameComplex"}, + {"CharFontPitch", "CharFontPitch"}, + {"CharFontPitchAsian", "CharFontPitchAsian"}, + {"CharFontPitchComplex", "CharFontPitchComplex"}, + {"CharFontStyleName", "CharFontStyleName"}, + {"CharFontStyleNameAsian", "CharFontStyleNameAsian"}, + {"CharFontStyleNameComplex", "CharFontStyleNameComplex"}, + + {"CharHeight", "CharHeight"}, + {"CharHeightAsian", "CharHeightAsian"}, + {"CharHeightComplex", "CharHeightComplex"}, + {"CharKerning", "CharKerning"}, + {"CharLocale", "CharLocale"}, + {"CharLocaleAsian", "CharLocaleAsian"}, + {"CharLocaleComplex", "CharLocaleComplex"}, + {"CharPosture", "CharPosture"}, + {"CharPostureAsian", "CharPostureAsian"}, + {"CharPostureComplex", "CharPostureComplex"}, + {"CharRelief", "CharRelief"}, + {"CharShadowed", "CharShadowed"}, + {"CharStrikeout", "CharStrikeout"}, + {"CharUnderline", "CharUnderline"}, + {"CharUnderlineColor", "CharUnderlineColor"}, + {"CharUnderlineHasColor", "CharUnderlineHasColor"}, + {"CharOverline", "CharOverline"}, + {"CharOverlineColor", "CharOverlineColor"}, + {"CharOverlineHasColor", "CharOverlineHasColor"}, + {"CharWeight", "CharWeight"}, + {"CharWeightAsian", "CharWeightAsian"}, + {"CharWeightComplex", "CharWeightComplex"}, + {"CharWordMode", "CharWordMode"}, + + {"WritingMode", "WritingMode"}, + + {"ParaIsCharacterDistance", "ParaIsCharacterDistance"}}; + + return s_aShapePropertyMapForCharacterProperties; +} + +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForParagraphProperties() +{ + //shape property -- chart model object property + static tPropertyNameMap s_aShapePropertyMapForParagraphProperties{ + {"ParaAdjust", "ParaAdjust"}, + {"ParaBottomMargin", "ParaBottomMargin"}, + {"ParaIsHyphenation", "ParaIsHyphenation"}, + {"ParaLastLineAdjust", "ParaLastLineAdjust"}, + {"ParaLeftMargin", "ParaLeftMargin"}, + {"ParaRightMargin", "ParaRightMargin"}, + {"ParaTopMargin", "ParaTopMargin"}}; + return s_aShapePropertyMapForParagraphProperties; +} + +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForFillProperties() +{ + //shape property -- chart model object property + static tPropertyNameMap s_aShapePropertyMapForFillProperties{ + {"FillBackground", "FillBackground"}, + {"FillBitmapName", "FillBitmapName"}, + {"FillColor", "FillColor"}, + {"FillGradientName", "FillGradientName"}, + {"FillGradientStepCount", "FillGradientStepCount"}, + {"FillHatchName", "FillHatchName"}, + {"FillStyle", "FillStyle"}, + {"FillTransparence", "FillTransparence"}, + {"FillTransparenceGradientName", "FillTransparenceGradientName"}, + //bitmap properties + {"FillBitmapMode", "FillBitmapMode"}, + {"FillBitmapSizeX", "FillBitmapSizeX"}, + {"FillBitmapSizeY", "FillBitmapSizeY"}, + {"FillBitmapLogicalSize", "FillBitmapLogicalSize"}, + {"FillBitmapOffsetX", "FillBitmapOffsetX"}, + {"FillBitmapOffsetY", "FillBitmapOffsetY"}, + {"FillBitmapRectanglePoint", "FillBitmapRectanglePoint"}, + {"FillBitmapPositionOffsetX", "FillBitmapPositionOffsetX"}, + {"FillBitmapPositionOffsetY", "FillBitmapPositionOffsetY"}}; + return s_aShapePropertyMapForFillProperties; +} + +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForLineProperties() +{ + //shape property -- chart model object property + static tPropertyNameMap s_aShapePropertyMapForLineProperties{ + {"LineColor", "LineColor"}, + {"LineDashName", "LineDashName"}, + {"LineJoint", "LineJoint"}, + {"LineStyle", "LineStyle"}, + {"LineTransparence", "LineTransparence"}, + {"LineWidth", "LineWidth"}, + {"LineCap", "LineCap"}}; + return s_aShapePropertyMapForLineProperties; +} + +namespace { + tPropertyNameMap getPropertyNameMapForFillAndLineProperties_() { + auto map = PropertyMapper::getPropertyNameMapForFillProperties(); + auto const & add + = PropertyMapper::getPropertyNameMapForLineProperties(); + map.insert(add.begin(), add.end()); + return map; + } +} +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForFillAndLineProperties() +{ + static tPropertyNameMap s_aShapePropertyMapForFillAndLineProperties + = getPropertyNameMapForFillAndLineProperties_(); + return s_aShapePropertyMapForFillAndLineProperties; +} + +namespace { + tPropertyNameMap getPropertyNameMapForTextShapeProperties_() { + auto map = PropertyMapper::getPropertyNameMapForCharacterProperties(); + auto const & add1 + = PropertyMapper::getPropertyNameMapForFillProperties(); + map.insert(add1.begin(), add1.end()); + auto const & add2 + = PropertyMapper::getPropertyNameMapForLineProperties(); + map.insert(add2.begin(), add2.end()); + return map; + } +} +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForTextShapeProperties() +{ + static tPropertyNameMap s_aShapePropertyMapForTextShapeProperties + = getPropertyNameMapForTextShapeProperties_(); + return s_aShapePropertyMapForTextShapeProperties; +} + +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForLineSeriesProperties() +{ + //shape property -- chart model object property + static tPropertyNameMap s_aShapePropertyMapForLineSeriesProperties{ + {"LineColor", "Color"}, + {"LineDashName", "LineDashName"}, + {"LineStyle", "LineStyle"}, + {"LineTransparence", "Transparency"}, + {"LineWidth", "LineWidth"}, + {"LineCap", "LineCap"}}; + return s_aShapePropertyMapForLineSeriesProperties; +} + +namespace { + tPropertyNameMap getPropertyNameMapForTextLabelProperties_() { + auto map = PropertyMapper::getPropertyNameMapForCharacterProperties(); + map.insert({ + {"LineStyle", CHART_UNONAME_LABEL_BORDER_STYLE}, + {"LineWidth", CHART_UNONAME_LABEL_BORDER_WIDTH}, + {"LineColor", CHART_UNONAME_LABEL_BORDER_COLOR}, + {"LineTransparence", CHART_UNONAME_LABEL_BORDER_TRANS}, + {"FillStyle", CHART_UNONAME_LABEL_FILL_STYLE}, + {"FillColor", CHART_UNONAME_LABEL_FILL_COLOR}, + {"FillBackground", CHART_UNONAME_LABEL_FILL_BACKGROUND}, + {"FillHatchName", CHART_UNONAME_LABEL_FILL_HATCH_NAME} + }); + // fix the spelling! + return map; + } +} +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForTextLabelProperties() +{ + // target name (drawing layer) : source name (chart model) + static tPropertyNameMap aMap = getPropertyNameMapForTextLabelProperties_(); + return aMap; +} + +const tPropertyNameMap& PropertyMapper::getPropertyNameMapForFilledSeriesProperties() +{ + //shape property -- chart model object property + static tPropertyNameMap s_aShapePropertyMapForFilledSeriesProperties{ + {"FillBackground", "FillBackground"}, + {"FillBitmapName", "FillBitmapName"}, + {"FillColor", "Color"}, + {"FillGradientName", "GradientName"}, + {"FillGradientStepCount", "GradientStepCount"}, + {"FillHatchName", "HatchName"}, + {"FillStyle", "FillStyle"}, + {"FillTransparence", "Transparency"}, + {"FillTransparenceGradientName", "TransparencyGradientName"}, + //bitmap properties + {"FillBitmapMode", "FillBitmapMode"}, + {"FillBitmapSizeX", "FillBitmapSizeX"}, + {"FillBitmapSizeY", "FillBitmapSizeY"}, + {"FillBitmapLogicalSize", "FillBitmapLogicalSize"}, + {"FillBitmapOffsetX", "FillBitmapOffsetX"}, + {"FillBitmapOffsetY", "FillBitmapOffsetY"}, + {"FillBitmapRectanglePoint", "FillBitmapRectanglePoint"}, + {"FillBitmapPositionOffsetX", "FillBitmapPositionOffsetX"}, + {"FillBitmapPositionOffsetY", "FillBitmapPositionOffsetY"}, + //line properties + {"LineColor", "BorderColor"}, + {"LineDashName", "BorderDashName"}, + {"LineStyle", "BorderStyle"}, + {"LineTransparence", "BorderTransparency"}, + {"LineWidth", "BorderWidth"}, + {"LineCap", "LineCap"}}; + return s_aShapePropertyMapForFilledSeriesProperties; +} + +void PropertyMapper::setMultiProperties( + const tNameSequence& rNames + , const tAnySequence& rValues + , const css::uno::Reference< + css::beans::XPropertySet >& xTarget ) +{ + bool bSuccess = false; + try + { + uno::Reference< beans::XMultiPropertySet > xShapeMultiProp( xTarget, uno::UNO_QUERY ); + if( xShapeMultiProp.is() ) + { + xShapeMultiProp->setPropertyValues( rNames, rValues ); + bSuccess = true; + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); //if this occurs more often think of removing the XMultiPropertySet completely for better performance + } + + if(bSuccess) + return; + + try + { + sal_Int32 nCount = std::max( rNames.getLength(), rValues.getLength() ); + OUString aPropName; + uno::Any aValue; + for( sal_Int32 nN = 0; nN < nCount; nN++ ) + { + aPropName = rNames[nN]; + aValue = rValues[nN]; + + try + { + xTarget->setPropertyValue( aPropName, aValue ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } +} + +void PropertyMapper::getTextLabelMultiPropertyLists( + const uno::Reference< beans::XPropertySet >& xSourceProp + , tNameSequence& rPropNames, tAnySequence& rPropValues + , bool bName + , sal_Int32 nLimitedSpace + , bool bLimitedHeight + , bool bSupportsLabelBorder) +{ + //fill character properties into the ValueMap + tPropertyNameValueMap aValueMap; + tPropertyNameMap const & aNameMap = bSupportsLabelBorder ? PropertyMapper::getPropertyNameMapForTextLabelProperties() : getPropertyNameMapForCharacterProperties(); + + PropertyMapper::getValueMap(aValueMap, aNameMap, xSourceProp); + + //some more shape properties apart from character properties, position-matrix and label string + aValueMap.emplace( "TextHorizontalAdjust", uno::Any(drawing::TextHorizontalAdjust_CENTER) ); // drawing::TextHorizontalAdjust - needs to be overwritten + aValueMap.emplace( "TextVerticalAdjust", uno::Any(drawing::TextVerticalAdjust_CENTER) ); //drawing::TextVerticalAdjust - needs to be overwritten + aValueMap.emplace( "TextAutoGrowHeight", uno::Any(true) ); // sal_Bool + aValueMap.emplace( "TextAutoGrowWidth", uno::Any(true) ); // sal_Bool + if( bName ) + aValueMap.emplace( "Name", uno::Any( OUString() ) ); //CID OUString - needs to be overwritten for each point + + if( nLimitedSpace > 0 ) + { + if(bLimitedHeight) + aValueMap.emplace( "TextMaximumFrameHeight", uno::Any(nLimitedSpace) ); //sal_Int32 + else + aValueMap.emplace( "TextMaximumFrameWidth", uno::Any(nLimitedSpace) ); //sal_Int32 + aValueMap.emplace( "ParaIsHyphenation", uno::Any(true) ); + } + + PropertyMapper::getMultiPropertyListsFromValueMap( rPropNames, rPropValues, aValueMap ); +} + +void PropertyMapper::getPreparedTextShapePropertyLists( + const uno::Reference< beans::XPropertySet >& xSourceProp + , tNameSequence& rPropNames, tAnySequence& rPropValues ) +{ + //fill character, line and fill properties into the ValueMap + tPropertyNameValueMap aValueMap; + PropertyMapper::getValueMap( aValueMap + , PropertyMapper::getPropertyNameMapForTextShapeProperties() + , xSourceProp ); + + // auto-grow makes sure the shape has the correct size after setting text + aValueMap.emplace( "TextHorizontalAdjust", uno::Any( drawing::TextHorizontalAdjust_CENTER )); + aValueMap.emplace( "TextVerticalAdjust", uno::Any( drawing::TextVerticalAdjust_CENTER )); + aValueMap.emplace( "TextAutoGrowHeight", uno::Any( true )); + aValueMap.emplace( "TextAutoGrowWidth", uno::Any( true )); + + // set some distance to the border, in case it is shown + const sal_Int32 nWidthDist = 250; + const sal_Int32 nHeightDist = 125; + aValueMap.emplace( "TextLeftDistance", uno::Any( nWidthDist )); + aValueMap.emplace( "TextRightDistance", uno::Any( nWidthDist )); + aValueMap.emplace( "TextUpperDistance", uno::Any( nHeightDist )); + aValueMap.emplace( "TextLowerDistance", uno::Any( nHeightDist )); + + // use a line-joint showing the border of thick lines like two rectangles + // filled in between. + aValueMap["LineJoint"] <<= drawing::LineJoint_ROUND; + + PropertyMapper::getMultiPropertyListsFromValueMap( rPropNames, rPropValues, aValueMap ); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/ShapeFactory.cxx b/chart2/source/view/main/ShapeFactory.cxx new file mode 100644 index 000000000..6ccaf9fb6 --- /dev/null +++ b/chart2/source/view/main/ShapeFactory.cxx @@ -0,0 +1,2764 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <ShapeFactory.hxx> +#include <BaseGFXHelper.hxx> +#include <ViewDefines.hxx> +#include <Stripe.hxx> +#include <CommonConverters.hxx> +#include <RelativeSizeHelper.hxx> +#include <PropertyMapper.hxx> +#include <VLineProperties.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/chart2/XFormattedString.hpp> +#include <com/sun/star/drawing/CircleKind.hpp> +#include <com/sun/star/drawing/DoubleSequence.hpp> +#include <com/sun/star/drawing/FlagSequence.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/NormalsKind.hpp> +#include <com/sun/star/drawing/PointSequence.hpp> +#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/TextureProjectionMode.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XShapes2.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Any.hxx> + +#include <editeng/unoprnms.hxx> +#include <rtl/math.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <tools/diagnose_ex.h> +#include <tools/helpers.hxx> +#include <sal/log.hxx> + +#include <algorithm> + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Reference; + +namespace chart +{ + +namespace +{ + +void lcl_addProperty(uno::Sequence<OUString> & rPropertyNames, uno::Sequence<uno::Any> & rPropertyValues, + OUString const & rName, uno::Any const & rAny) +{ + rPropertyNames.realloc(rPropertyNames.getLength() + 1); + rPropertyNames[rPropertyNames.getLength() - 1] = rName; + + rPropertyValues.realloc(rPropertyValues.getLength() + 1); + rPropertyValues[rPropertyValues.getLength() - 1] = rAny; +} + +} // end anonymous namespace + +uno::Reference< drawing::XShapes > ShapeFactory::getOrCreateChartRootShape( + const uno::Reference< drawing::XDrawPage>& xDrawPage ) +{ + uno::Reference<drawing::XShapes> xRet = ShapeFactory::getChartRootShape(xDrawPage); + if (xRet.is()) + return xRet; + + // Create a new root shape and set it to the bottom of the page. The root + // shape is identified by having the name com.sun.star.chart2.shapes. + uno::Reference<drawing::XShape> xShape( + m_xShapeFactory->createInstance("com.sun.star.drawing.GroupShape"), uno::UNO_QUERY); + uno::Reference<drawing::XShapes2> xShapes2(xDrawPage, uno::UNO_QUERY_THROW); + xShapes2->addBottom(xShape); + + setShapeName(xShape, "com.sun.star.chart2.shapes"); + xShape->setSize(awt::Size(0,0)); + + xRet.set(xShape, uno::UNO_QUERY); + return xRet; +} + +void ShapeFactory::setPageSize(const uno::Reference<drawing::XShapes>&, const awt::Size&) {} + +// diverse tools::PolyPolygon create methods + +static uno::Any createPolyPolygon_Cube( + const drawing::Direction3D& rSize, double fRoundedEdge, bool bRounded ) +{ + OSL_PRECOND(fRoundedEdge>=0, "fRoundedEdge needs to be >= 0"); + + // always use extra points, so set percent diagonal to 0.4 which is 0% in the UI (old Chart comment) + if( fRoundedEdge == 0.0 && bRounded) + fRoundedEdge = 0.4 / 200.0; + else if(!bRounded) + fRoundedEdge = 0.0; + + //fWidthH stands for Half Width + const double fWidthH = rSize.DirectionX >=0.0? rSize.DirectionX/2.0 : -rSize.DirectionX/2.0; + const double fHeight = rSize.DirectionY; + + const double fHeightSign = fHeight >= 0.0 ? 1.0 : -1.0; + + const double fOffset = (fWidthH * fRoundedEdge) * 1.05; // increase by 5% for safety + const bool bRoundEdges = fRoundedEdge && fOffset < fWidthH && 2.0 * fOffset < fHeightSign*fHeight; + const sal_Int32 nPointCount = bRoundEdges ? 13 : 5; + + drawing::PolyPolygonShape3D aPP; + + aPP.SequenceX.realloc(1); + aPP.SequenceY.realloc(1); + aPP.SequenceZ.realloc(1); + + drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray(); + + pOuterSequenceX->realloc(nPointCount); + pOuterSequenceY->realloc(nPointCount); + pOuterSequenceZ->realloc(nPointCount); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + for(sal_Int32 nN = nPointCount; nN--;) + *pInnerSequenceZ++ = 0.0; + + if(nPointCount == 5) + { + *pInnerSequenceY++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceY++ = fHeight; + *pInnerSequenceY++ = fHeight; + *pInnerSequenceY++ = 0.0; + + *pInnerSequenceX++ = -fWidthH; + *pInnerSequenceX++ = fWidthH; + *pInnerSequenceX++ = fWidthH; + *pInnerSequenceX++ = -fWidthH; + *pInnerSequenceX++ = -fWidthH; + } + else + { + *pInnerSequenceY++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceY++ = fHeightSign*fOffset; + *pInnerSequenceY++ = fHeight - fHeightSign*fOffset; + *pInnerSequenceY++ = fHeight; + *pInnerSequenceY++ = fHeight; + *pInnerSequenceY++ = fHeight; + *pInnerSequenceY++ = fHeight; + *pInnerSequenceY++ = fHeight - fHeightSign*fOffset; + *pInnerSequenceY++ = fHeightSign*fOffset; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceY++ = 0.0; + + *pInnerSequenceX++ = -fWidthH + fOffset; + *pInnerSequenceX++ = fWidthH - fOffset; + *pInnerSequenceX++ = fWidthH; + *pInnerSequenceX++ = fWidthH; + *pInnerSequenceX++ = fWidthH; + *pInnerSequenceX++ = fWidthH; + *pInnerSequenceX++ = fWidthH - fOffset; + *pInnerSequenceX++ = -fWidthH + fOffset; + *pInnerSequenceX++ = -fWidthH; + *pInnerSequenceX++ = -fWidthH; + *pInnerSequenceX++ = -fWidthH; + *pInnerSequenceX++ = -fWidthH; + *pInnerSequenceX++ = -fWidthH + fOffset; + } + return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get()); +} + +static uno::Any createPolyPolygon_Cylinder( + double fHeight + , double fRadius + , sal_Int32& nVerticalSegmentCount ) +{ + //fHeight may be negative + OSL_PRECOND(fRadius>0, "The radius of a cylinder needs to be > 0"); + + drawing::PolyPolygonShape3D aPP; + + nVerticalSegmentCount=1; + + aPP.SequenceX.realloc(3); + aPP.SequenceY.realloc(3); + aPP.SequenceZ.realloc(3); + + drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray(); + + pOuterSequenceX->realloc(2); + pOuterSequenceY->realloc(2); + pOuterSequenceZ->realloc(2); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + double fY1 = 0.0; + double fY2 = fHeight; + + if( fHeight<0.0 ) + std::swap(fY1,fY2); + + for(sal_Int32 nN = 2; nN--;) + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = fY1; + + *pInnerSequenceX++ = fRadius; + *pInnerSequenceY++ = fY1; + + pOuterSequenceX++;pOuterSequenceY++;pOuterSequenceZ++; + pOuterSequenceX->realloc(2); + pOuterSequenceY->realloc(2); + pOuterSequenceZ->realloc(2); + + pInnerSequenceX = pOuterSequenceX->getArray(); + pInnerSequenceY = pOuterSequenceY->getArray(); + pInnerSequenceZ = pOuterSequenceZ->getArray(); + + for(sal_Int32 nN = 2; nN--;) + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = fRadius; + *pInnerSequenceY++ = fY1; + + *pInnerSequenceX++ = fRadius; + *pInnerSequenceY++ = fY2; + + pOuterSequenceX++;pOuterSequenceY++;pOuterSequenceZ++; + pOuterSequenceX->realloc(2); + pOuterSequenceY->realloc(2); + pOuterSequenceZ->realloc(2); + + pInnerSequenceX = pOuterSequenceX->getArray(); + pInnerSequenceY = pOuterSequenceY->getArray(); + pInnerSequenceZ = pOuterSequenceZ->getArray(); + + for(sal_Int32 nN = 2; nN--;) + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = fRadius; + *pInnerSequenceY++ = fY2; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = fY2; + + return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get()); +} + +static uno::Any createPolyPolygon_Cone( double fHeight, double fRadius, double fTopHeight + , sal_Int32& nVerticalSegmentCount ) +{ + OSL_PRECOND(fRadius>0, "The radius of a cone needs to be > 0"); + + //for stacked charts we need cones without top -> fTopHeight != 0 resp. bTopless == true + //fTopHeight indicates the high of the cutted top only (not the full height) + bool bTopless = !::rtl::math::approxEqual( fHeight, fHeight + fTopHeight ); + + double r1= 0.0, r2 = fRadius; + if(bTopless) + // #i63212# fHeight may be negative, fTopHeight is always positive -> use fabs(fHeight) + r1 = fRadius * fTopHeight/(fabs(fHeight)+fTopHeight); + + nVerticalSegmentCount=1; + drawing::PolyPolygonShape3D aPP; + + aPP.SequenceX.realloc(2); + aPP.SequenceY.realloc(2); + aPP.SequenceZ.realloc(2); + + drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray(); + + pOuterSequenceX->realloc(2); + pOuterSequenceY->realloc(2); + pOuterSequenceZ->realloc(2); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + double fX1 = 0.0; + double fX2 = r2; + double fX3 = r1; + + double fY1 = 0.0; + double fY2 = 0.0; + double fY3 = fHeight; + + if( fHeight<0.0 ) + { + std::swap(fX1,fX3); + std::swap(fY1,fY3); + } + + for(sal_Int32 nN = 2; nN--;) + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceY++ = fY1; + *pInnerSequenceX++ = fX1; + + *pInnerSequenceY++ = fY2; + *pInnerSequenceX++ = fX2; + + pOuterSequenceX++;pOuterSequenceY++;pOuterSequenceZ++; + pOuterSequenceX->realloc(2); + pOuterSequenceY->realloc(2); + pOuterSequenceZ->realloc(2); + + pInnerSequenceX = pOuterSequenceX->getArray(); + pInnerSequenceY = pOuterSequenceY->getArray(); + pInnerSequenceZ = pOuterSequenceZ->getArray(); + + for(sal_Int32 nN = 2; nN--;) + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceY++ = fY2; + *pInnerSequenceX++ = fX2; + + *pInnerSequenceY++ = fY3; + *pInnerSequenceX++ = fX3; + + return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get()); +} + +// methods for 3D shape creation + +uno::Reference<drawing::XShape> + ShapeFactory::createCube( + const uno::Reference<drawing::XShapes>& xTarget + , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize + , sal_Int32 nRotateZAngleHundredthDegree + , const uno::Reference< beans::XPropertySet >& xSourceProp + , const tPropertyNameMap& rPropertyNameMap + , bool bRounded ) +{ + if( !xTarget.is() ) + return nullptr; + if( bRounded ) + { + try + { + if( xSourceProp.is() ) + { + drawing::LineStyle aLineStyle; + xSourceProp->getPropertyValue( "BorderStyle" ) >>= aLineStyle; + if( aLineStyle == drawing::LineStyle_SOLID ) + bRounded = false; + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + uno::Reference<drawing::XShape> xShape = impl_createCube( xTarget, rPosition, rSize, nRotateZAngleHundredthDegree, bRounded ); + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + if( xSourceProp.is()) + PropertyMapper::setMappedProperties( xProp, xSourceProp, rPropertyNameMap ); + return xShape; +} + +uno::Reference<drawing::XShape> + ShapeFactory::impl_createCube( + const uno::Reference<drawing::XShapes>& xTarget + , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize + , sal_Int32 nRotateZAngleHundredthDegree + , bool bRounded ) +{ + if( !xTarget.is() ) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.Shape3DExtrudeObject" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set properties + uno::Reference<beans::XMultiPropertySet> xMultiPropertySet(xShape, uno::UNO_QUERY); + OSL_ENSURE(xMultiPropertySet.is(), "created shape offers no XMultiPropertySet"); + if (xMultiPropertySet.is()) + { + try + { + //depth + double fDepth = rSize.DirectionZ; + if (fDepth<0) + fDepth*=-1.0; + + //PercentDiagonal + sal_Int16 nPercentDiagonal = bRounded ? 3 : 0; + + //Matrix for position + basegfx::B3DHomMatrix aHomMatrix; + if (nRotateZAngleHundredthDegree != 0) + aHomMatrix.rotate(0.0, 0.0, -nRotateZAngleHundredthDegree / 18000.00 * F_PI); + aHomMatrix.translate(rPosition.PositionX, rPosition.PositionY, + rPosition.PositionZ - (fDepth / 2.0)); + + uno::Sequence<OUString> aPropertyNames { + UNO_NAME_3D_EXTRUDE_DEPTH, + UNO_NAME_3D_PERCENT_DIAGONAL, + UNO_NAME_3D_POLYPOLYGON3D, + UNO_NAME_3D_TRANSFORM_MATRIX, + }; + + uno::Sequence<uno::Any> aPropertyValues { + uno::Any(sal_Int32(fDepth)), // Depth + uno::Any(nPercentDiagonal), // PercentDiagonal + createPolyPolygon_Cube(rSize, double(nPercentDiagonal) / 200.0, bRounded), + uno::Any(B3DHomMatrixToHomogenMatrix(aHomMatrix)) + }; + + xMultiPropertySet->setPropertyValues(aPropertyNames, aPropertyValues); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference<drawing::XShape> + ShapeFactory::createCylinder( + const uno::Reference<drawing::XShapes>& xTarget + , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize + , sal_Int32 nRotateZAngleHundredthDegree ) +{ + return impl_createConeOrCylinder( + xTarget, rPosition, rSize, 0.0, nRotateZAngleHundredthDegree, true ); +} + +uno::Reference<drawing::XShape> + ShapeFactory::createPyramid( + const uno::Reference<drawing::XShapes>& xTarget + , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize + , double fTopHeight, bool bRotateZ + , const uno::Reference< beans::XPropertySet >& xSourceProp + , const tPropertyNameMap& rPropertyNameMap ) +{ + if( !xTarget.is() ) + return nullptr; + + Reference< drawing::XShapes > xGroup( ShapeFactory::createGroup3D( xTarget ) ); + + bool bDoubleSided = false; + short nRotatedTexture = 0; + + const double fWidth = rSize.DirectionX; + const double fDepth = rSize.DirectionZ; + const double fHeight = rSize.DirectionY; + + drawing::Position3D aBottomP1( rPosition.PositionX, rPosition.PositionY, rPosition.PositionZ - fDepth/2.0 ); + if(bRotateZ) + aBottomP1.PositionY -= fWidth/2.0; + else + aBottomP1.PositionX -= fWidth/2.0; + drawing::Position3D aBottomP2( aBottomP1 ); + if(bRotateZ) + aBottomP2.PositionY += fWidth; + else + aBottomP2.PositionX += fWidth; + drawing::Position3D aBottomP3( aBottomP2 ); + drawing::Position3D aBottomP4( aBottomP1 ); + aBottomP3.PositionZ += fDepth; + aBottomP4.PositionZ += fDepth; + + const double fTopFactor = fTopHeight/(fabs(fHeight)+fTopHeight); + drawing::Position3D aTopP1( rPosition.PositionX, rPosition.PositionY, rPosition.PositionZ - fDepth*fTopFactor/2.0 ); + if(bRotateZ) + { + aTopP1.PositionY -= fWidth*fTopFactor/2.0; + aTopP1.PositionX += fHeight; + } + else + { + aTopP1.PositionX -= fWidth*fTopFactor/2.0; + aTopP1.PositionY += fHeight; + } + drawing::Position3D aTopP2( aTopP1 ); + if(bRotateZ) + aTopP2.PositionY += fWidth*fTopFactor; + else + aTopP2.PositionX += fWidth*fTopFactor; + drawing::Position3D aTopP3( aTopP2 ); + drawing::Position3D aTopP4( aTopP1 ); + aTopP3.PositionZ += fDepth*fTopFactor; + aTopP4.PositionZ += fDepth*fTopFactor; + + Stripe aStripeBottom( aBottomP1, aBottomP4, aBottomP3, aBottomP2 ); + + drawing::Position3D aNormalsBottomP1( aBottomP1 ); + drawing::Position3D aNormalsBottomP2( aBottomP2 ); + drawing::Position3D aNormalsBottomP3( aBottomP3 ); + drawing::Position3D aNormalsBottomP4( aBottomP4 ); + drawing::Position3D aNormalsTopP1( aBottomP1 ); + drawing::Position3D aNormalsTopP2( aBottomP2 ); + drawing::Position3D aNormalsTopP3( aBottomP3 ); + drawing::Position3D aNormalsTopP4( aBottomP4 ); + if( bRotateZ ) + { + aNormalsTopP1.PositionX += fHeight; + aNormalsTopP2.PositionX += fHeight; + aNormalsTopP3.PositionX += fHeight; + aNormalsTopP4.PositionX += fHeight; + } + else + { + aNormalsTopP1.PositionY += fHeight; + aNormalsTopP2.PositionY += fHeight; + aNormalsTopP3.PositionY += fHeight; + aNormalsTopP4.PositionY += fHeight; + } + + bool bInvertPolygon = false; + bool bInvertNormals = false; + + if(bRotateZ) + { + //bars + if(fHeight>=0.0) + { + nRotatedTexture = 2; + bInvertNormals = true; + aStripeBottom = Stripe( aBottomP1, aBottomP4, aBottomP3, aBottomP2 ); + } + else + { + bInvertPolygon = true; + nRotatedTexture = 1; + aStripeBottom = Stripe( aBottomP2, aBottomP3, aBottomP4, aBottomP1 ); + } + } + else + { + //columns + if(fHeight>=0.0) + { + bInvertPolygon = true; + nRotatedTexture = 2; + aStripeBottom = Stripe( aBottomP2, aBottomP3, aBottomP4, aBottomP1 ); + } + else + { + nRotatedTexture = 3; + bInvertNormals = true; + aStripeBottom = Stripe( aBottomP4, aBottomP3, aBottomP2, aBottomP1 ); + } + } + aStripeBottom.InvertNormal(true); + + Stripe aStripe1( aTopP2, aTopP1, aBottomP1, aBottomP2 ); + Stripe aStripe2( aTopP3, aTopP2, aBottomP2, aBottomP3 ); + Stripe aStripe3( aTopP4, aTopP3, aBottomP3, aBottomP4 ); + Stripe aStripe4( aTopP1, aTopP4, aBottomP4, aBottomP1 ); + + if( bInvertPolygon ) + { + aStripe1 = Stripe( aBottomP1, aTopP1, aTopP2, aBottomP2 ); + aStripe2 = Stripe( aBottomP2, aTopP2, aTopP3, aBottomP3 ); + aStripe3 = Stripe( aBottomP3, aTopP3, aTopP4, aBottomP4 ); + aStripe4 = Stripe( aBottomP4, aTopP4, aTopP1, aBottomP1 ); + } + + Stripe aNormalsStripe1( aNormalsTopP1, aNormalsBottomP1, aNormalsBottomP2, aNormalsTopP2 ); + Stripe aNormalsStripe2( aNormalsTopP2, aNormalsBottomP2, aNormalsBottomP3, aNormalsTopP3 ); + Stripe aNormalsStripe3( aNormalsTopP3, aNormalsBottomP3, aNormalsBottomP4, aNormalsTopP4 ); + Stripe aNormalsStripe4( aNormalsTopP4, aNormalsBottomP4, aNormalsBottomP1, aNormalsTopP1 ); + + if( bInvertNormals ) + { + aNormalsStripe1 = Stripe( aNormalsTopP2, aNormalsBottomP2, aNormalsBottomP1, aNormalsTopP1 ); + aNormalsStripe2 = Stripe( aNormalsTopP3, aNormalsBottomP3, aNormalsBottomP2, aNormalsTopP2 ); + aNormalsStripe3 = Stripe( aNormalsTopP4, aNormalsBottomP4, aNormalsBottomP3, aNormalsTopP3 ); + aNormalsStripe4 = Stripe( aNormalsTopP1, aNormalsBottomP1, aNormalsBottomP4, aNormalsTopP4 ); + } + + aStripe1.SetManualNormal( aNormalsStripe1.getNormal() ); + aStripe2.SetManualNormal( aNormalsStripe2.getNormal() ); + aStripe3.SetManualNormal( aNormalsStripe3.getNormal() ); + aStripe4.SetManualNormal( aNormalsStripe4.getNormal() ); + + const bool bFlatNormals = false; + ShapeFactory::createStripe( xGroup, aStripe1, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals ); + ShapeFactory::createStripe( xGroup, aStripe2, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals ); + ShapeFactory::createStripe( xGroup, aStripe3, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals ); + ShapeFactory::createStripe( xGroup, aStripe4, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals ); + ShapeFactory::createStripe( xGroup, aStripeBottom, xSourceProp, rPropertyNameMap, bDoubleSided, nRotatedTexture, bFlatNormals ); + + return Reference< drawing::XShape >( xGroup, uno::UNO_QUERY ); +} + +uno::Reference<drawing::XShape> + ShapeFactory::createCone( + const uno::Reference<drawing::XShapes>& xTarget + , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize + , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree ) +{ + return impl_createConeOrCylinder( xTarget, rPosition, rSize, fTopHeight, nRotateZAngleHundredthDegree, false ); +} + +uno::Reference<drawing::XShape> + ShapeFactory::impl_createConeOrCylinder( + const uno::Reference<drawing::XShapes>& xTarget + , const drawing::Position3D& rPosition, const drawing::Direction3D& rSize + , double fTopHeight, sal_Int32 nRotateZAngleHundredthDegree + , bool bCylinder ) +{ + if( !xTarget.is() ) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.Shape3DLatheObject" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + double fWidth = rSize.DirectionX/2.0; //The depth will be corrected within Matrix + double fRadius = fWidth; //!!!!!!!! problem in drawing layer: rotation object calculates wrong needed size -> wrong camera (it's a problem with bounding boxes) + double fHeight = rSize.DirectionY; + + //set properties + uno::Reference<beans::XMultiPropertySet> xMultiPropertySet(xShape, uno::UNO_QUERY); + OSL_ENSURE(xMultiPropertySet.is(), "created shape offers no XMultiPropertySet"); + if (xMultiPropertySet.is()) + { + try + { + //Polygon + sal_Int32 nVerticalSegmentCount = 0; + uno::Any aPPolygon = bCylinder + ? createPolyPolygon_Cylinder(fHeight, fRadius, nVerticalSegmentCount) + : createPolyPolygon_Cone(fHeight, fRadius, fTopHeight, nVerticalSegmentCount); + + //Matrix for position + basegfx::B3DHomMatrix aHomMatrix; + if (nRotateZAngleHundredthDegree != 0) + aHomMatrix.rotate(0.0,0.0,-nRotateZAngleHundredthDegree/18000.00*F_PI); + //stretch the symmetric objects to given depth + aHomMatrix.scale(1.0,1.0,rSize.DirectionZ/rSize.DirectionX); + aHomMatrix.translate(rPosition.PositionX, rPosition.PositionY, rPosition.PositionZ); + + uno::Sequence<OUString> aPropertyNames{ + UNO_NAME_3D_PERCENT_DIAGONAL, + UNO_NAME_3D_POLYPOLYGON3D, + UNO_NAME_3D_TRANSFORM_MATRIX, + UNO_NAME_3D_HORZ_SEGS, + UNO_NAME_3D_VERT_SEGS, + UNO_NAME_3D_REDUCED_LINE_GEOMETRY + }; + + uno::Sequence<uno::Any> aPropertyValues { + uno::Any(sal_Int16(5)), // PercentDiagonal + aPPolygon, // Polygon + uno::Any(B3DHomMatrixToHomogenMatrix(aHomMatrix)), // Matrix + uno::Any(CHART_3DOBJECT_SEGMENTCOUNT), // Horizontal Segments + uno::Any(nVerticalSegmentCount), // Vertical Segments + uno::Any(true) // Reduced lines + }; + + xMultiPropertySet->setPropertyValues(aPropertyNames, aPropertyValues); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +static void appendAndCloseBezierCoords( drawing::PolyPolygonBezierCoords& rReturn, const drawing::PolyPolygonBezierCoords& rAdd, bool bAppendInverse ) +{ + if(!rAdd.Coordinates.hasElements()) + return; + sal_Int32 nAddCount = rAdd.Coordinates[0].getLength(); + if(!nAddCount) + return; + + sal_Int32 nOldCount = rReturn.Coordinates[0].getLength(); + + rReturn.Coordinates[0].realloc(nOldCount+nAddCount+1); + rReturn.Flags[0].realloc(nOldCount+nAddCount+1); + + for(sal_Int32 nN=0;nN<nAddCount; nN++ ) + { + sal_Int32 nAdd = bAppendInverse ? (nAddCount-1-nN) : nN; + rReturn.Coordinates[0][nOldCount+nN] = rAdd.Coordinates[0][nAdd]; + rReturn.Flags[0][nOldCount+nN] = rAdd.Flags[0][nAdd]; + } + + //close + rReturn.Coordinates[0][nOldCount+nAddCount] = rReturn.Coordinates[0][0]; + rReturn.Flags[0][nOldCount+nAddCount] = rReturn.Flags[0][0]; +} + +static drawing::PolyPolygonBezierCoords getCircularArcBezierCoords( + double fStartAngleRadian, double fWidthAngleRadian, double fUnitRadius + , const ::basegfx::B2DHomMatrix& rTransformationFromUnitCircle + , const double fAngleSubdivisionRadian ) +{ + //at least one polygon is created using two normal and two control points + //if the angle is larger it is separated into multiple sub angles + + drawing::PolyPolygonBezierCoords aReturn; + sal_Int32 nSegmentCount = static_cast< sal_Int32 >( fWidthAngleRadian/fAngleSubdivisionRadian ); + if( fWidthAngleRadian > fAngleSubdivisionRadian*nSegmentCount ) + nSegmentCount++; + + double fFirstSegmentAngle = fAngleSubdivisionRadian; + double fLastSegmentAngle = fAngleSubdivisionRadian; + if(nSegmentCount==1) + { + fFirstSegmentAngle = fWidthAngleRadian; + fLastSegmentAngle = 0.0; + } + else + { + double fFirstAngleOnSubDevision = (static_cast<sal_Int32>(fStartAngleRadian/fAngleSubdivisionRadian)+1)*fAngleSubdivisionRadian; + if( !::rtl::math::approxEqual( fStartAngleRadian, fFirstAngleOnSubDevision ) ) + fFirstSegmentAngle = fFirstAngleOnSubDevision-fStartAngleRadian; + + if(nSegmentCount>1) + { + fLastSegmentAngle = fWidthAngleRadian-fFirstSegmentAngle-fAngleSubdivisionRadian*(nSegmentCount-2); + if( fLastSegmentAngle<0 ) + nSegmentCount--; + if( fLastSegmentAngle>fAngleSubdivisionRadian ) + { + fLastSegmentAngle-=fAngleSubdivisionRadian; + nSegmentCount++; + } + } + } + + sal_Int32 nPointCount = 1 + 3*nSegmentCount; //first point of next segment equals last point of former segment + + aReturn.Coordinates = drawing::PointSequenceSequence(1); + aReturn.Flags = drawing::FlagSequenceSequence(1); + + drawing::PointSequence aPoints(nPointCount); + drawing::FlagSequence aFlags(nPointCount); + + //!! applying matrix to vector does ignore translation, so it is important to use a B2DPoint here instead of B2DVector + ::basegfx::B2DPoint P0,P1,P2,P3; + + sal_Int32 nPoint=0; + double fCurrentRotateAngle = fStartAngleRadian; + for(sal_Int32 nSegment=0; nSegment<nSegmentCount; nSegment++) + { + double fCurrentSegmentAngle = fAngleSubdivisionRadian; + if(nSegment==0)//first segment gets only a smaller peace until the next subdevision + fCurrentSegmentAngle = fFirstSegmentAngle; + else if(nSegment==(nSegmentCount-1)) //the last segment gets the rest angle that does not fit into equal pieces + fCurrentSegmentAngle = fLastSegmentAngle; + + //first create untransformed points for a unit circle arc: + const double fCos = cos(fCurrentSegmentAngle/2.0); + const double fSin = sin(fCurrentSegmentAngle/2.0); + P0.setX(fCos); + P3.setX(fCos); + P0.setY(-fSin); + P3.setY(-P0.getY()); + + P1.setX((4.0-fCos)/3.0); + P2.setX(P1.getX()); + P1.setY((1.0-fCos)*(fCos-3.0)/(3.0*fSin)); + P2.setY(-P1.getY()); + //transform thus startangle equals NULL + ::basegfx::B2DHomMatrix aStart; + aStart.rotate(fCurrentSegmentAngle/2.0 + fCurrentRotateAngle ); + fCurrentRotateAngle+=fCurrentSegmentAngle; + + aStart.scale( fUnitRadius, fUnitRadius ); + + //apply given transformation to get final points + P0 = rTransformationFromUnitCircle*(aStart*P0); + P1 = rTransformationFromUnitCircle*(aStart*P1); + P2 = rTransformationFromUnitCircle*(aStart*P2); + P3 = rTransformationFromUnitCircle*(aStart*P3); + + aPoints[nPoint].X = static_cast< sal_Int32 >( P0.getX()); + aPoints[nPoint].Y = static_cast< sal_Int32 >( P0.getY()); + aFlags [nPoint++] = drawing::PolygonFlags_NORMAL; + + aPoints[nPoint].X = static_cast< sal_Int32 >( P1.getX()); + aPoints[nPoint].Y = static_cast< sal_Int32 >( P1.getY()); + aFlags[nPoint++] = drawing::PolygonFlags_CONTROL; + + aPoints[nPoint].X = static_cast< sal_Int32 >( P2.getX()); + aPoints[nPoint].Y = static_cast< sal_Int32 >( P2.getY()); + aFlags [nPoint++] = drawing::PolygonFlags_CONTROL; + + if(nSegment==(nSegmentCount-1)) + { + aPoints[nPoint].X = static_cast< sal_Int32 >( P3.getX()); + aPoints[nPoint].Y = static_cast< sal_Int32 >( P3.getY()); + aFlags [nPoint++] = drawing::PolygonFlags_NORMAL; + } + } + + aReturn.Coordinates[0] = aPoints; + aReturn.Flags[0] = aFlags; + + return aReturn; +} + +static drawing::PolyPolygonBezierCoords getRingBezierCoords( + double fUnitCircleInnerRadius + , double fUnitCircleOuterRadius + , double fStartAngleRadian, double fWidthAngleRadian + , const ::basegfx::B2DHomMatrix& aTransformationFromUnitCircle + , const double fAngleSubdivisionRadian ) +{ + drawing::PolyPolygonBezierCoords aReturn; + + aReturn.Coordinates = drawing::PointSequenceSequence(1); + aReturn.Flags = drawing::FlagSequenceSequence(1); + + drawing::PolyPolygonBezierCoords aOuterArc = getCircularArcBezierCoords( + fStartAngleRadian, fWidthAngleRadian, fUnitCircleOuterRadius, aTransformationFromUnitCircle, fAngleSubdivisionRadian ); + aReturn.Coordinates[0] = aOuterArc.Coordinates[0]; + aReturn.Flags[0] = aOuterArc.Flags[0]; + + drawing::PolyPolygonBezierCoords aInnerArc = getCircularArcBezierCoords( + fStartAngleRadian, fWidthAngleRadian, fUnitCircleInnerRadius, aTransformationFromUnitCircle, fAngleSubdivisionRadian ); + appendAndCloseBezierCoords( aReturn, aInnerArc, true ); + + return aReturn; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createPieSegment2D( + const uno::Reference< drawing::XShapes >& xTarget + , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree + , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius + , const drawing::Direction3D& rOffset + , const drawing::HomogenMatrix& rUnitCircleToScene ) +{ + if( !xTarget.is() ) + return nullptr; + + // tdf#123504: both 0 and 360 are valid and different values here! + while (fUnitCircleWidthAngleDegree > 360) + fUnitCircleWidthAngleDegree -= 360.0; + while (fUnitCircleWidthAngleDegree < 0) + fUnitCircleWidthAngleDegree += 360.0; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.ClosedBezierShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); //need to add the shape before setting of properties + + //set properties + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + ::basegfx::B2DHomMatrix aTransformationFromUnitCircle( IgnoreZ( HomogenMatrixToB3DHomMatrix(rUnitCircleToScene) ) ); + aTransformationFromUnitCircle.translate(rOffset.DirectionX,rOffset.DirectionY); + + const double fAngleSubdivisionRadian = F_PI/10.0; + + drawing::PolyPolygonBezierCoords aCoords + = getRingBezierCoords(fUnitCircleInnerRadius, fUnitCircleOuterRadius, + basegfx::deg2rad(fUnitCircleStartAngleDegree), + basegfx::deg2rad(fUnitCircleWidthAngleDegree), + aTransformationFromUnitCircle, fAngleSubdivisionRadian); + + xProp->setPropertyValue( "PolyPolygonBezier", uno::Any( aCoords ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createPieSegment( + const uno::Reference< drawing::XShapes >& xTarget + , double fUnitCircleStartAngleDegree, double fUnitCircleWidthAngleDegree + , double fUnitCircleInnerRadius, double fUnitCircleOuterRadius + , const drawing::Direction3D& rOffset + , const drawing::HomogenMatrix& rUnitCircleToScene + , double fDepth ) +{ + if( !xTarget.is() ) + return nullptr; + + // tdf#123504: both 0 and 360 are valid and different values here! + while (fUnitCircleWidthAngleDegree > 360) + fUnitCircleWidthAngleDegree -= 360.0; + while (fUnitCircleWidthAngleDegree < 0) + fUnitCircleWidthAngleDegree += 360.0; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.Shape3DExtrudeObject" ), uno::UNO_QUERY ); + xTarget->add(xShape); //need to add the shape before setting of properties + + //set properties + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + ::basegfx::B2DHomMatrix aTransformationFromUnitCircle( IgnoreZ( HomogenMatrixToB3DHomMatrix(rUnitCircleToScene) ) ); + aTransformationFromUnitCircle.translate(rOffset.DirectionX,rOffset.DirectionY); + + const double fAngleSubdivisionRadian = F_PI/32.0; + + drawing::PolyPolygonBezierCoords aCoords + = getRingBezierCoords(fUnitCircleInnerRadius, fUnitCircleOuterRadius, + basegfx::deg2rad(fUnitCircleStartAngleDegree), + basegfx::deg2rad(fUnitCircleWidthAngleDegree), + aTransformationFromUnitCircle, fAngleSubdivisionRadian); + + //depth + xProp->setPropertyValue( UNO_NAME_3D_EXTRUDE_DEPTH + , uno::Any(static_cast<sal_Int32>(fDepth)) ); + + //PercentDiagonal + xProp->setPropertyValue( UNO_NAME_3D_PERCENT_DIAGONAL + , uno::Any( sal_Int16(0) ) ); + + //Polygon + drawing::PolyPolygonShape3D aPoly( BezierToPoly(aCoords) ); + ShapeFactory::closePolygon( aPoly ); + xProp->setPropertyValue( UNO_NAME_3D_POLYPOLYGON3D + , uno::Any( aPoly ) ); + + //DoubleSided + xProp->setPropertyValue( UNO_NAME_3D_DOUBLE_SIDED + , uno::Any( true ) ); + + //Reduced lines + xProp->setPropertyValue( UNO_NAME_3D_REDUCED_LINE_GEOMETRY + , uno::Any( true ) ); + + //TextureProjectionMode + xProp->setPropertyValue( UNO_NAME_3D_TEXTURE_PROJ_Y + , uno::Any( drawing::TextureProjectionMode_OBJECTSPECIFIC ) ); + + //TextureProjectionMode + xProp->setPropertyValue( UNO_NAME_3D_TEXTURE_PROJ_X + , uno::Any( drawing::TextureProjectionMode_PARALLEL ) ); + xProp->setPropertyValue( UNO_NAME_3D_TEXTURE_PROJ_Y + , uno::Any( drawing::TextureProjectionMode_OBJECTSPECIFIC ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createStripe( const uno::Reference< drawing::XShapes >& xTarget + , const Stripe& rStripe + , const uno::Reference< beans::XPropertySet >& xSourceProp + , const tPropertyNameMap& rPropertyNameMap + , bool bDoubleSided + , short nRotatedTexture + , bool bFlatNormals ) +{ + if( !xTarget.is() ) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.Shape3DPolygonObject" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set properties + uno::Reference<beans::XMultiPropertySet> xMultiPropertySet(xShape, uno::UNO_QUERY); + OSL_ENSURE(xMultiPropertySet.is(), "created shape offers no XMultiPropertySet"); + if (xMultiPropertySet.is()) + { + try + { + uno::Sequence<OUString> aPropertyNames{ + UNO_NAME_3D_POLYPOLYGON3D, + UNO_NAME_3D_TEXTUREPOLYGON3D, + UNO_NAME_3D_NORMALSPOLYGON3D, + UNO_NAME_3D_LINEONLY, + UNO_NAME_3D_DOUBLE_SIDED + }; + + uno::Sequence<uno::Any> aPropertyValues { + rStripe.getPolyPolygonShape3D(), // Polygon + Stripe::getTexturePolygon(nRotatedTexture), // TexturePolygon + rStripe.getNormalsPolygon(), // Normals Polygon + uno::Any(false), // LineOnly + uno::Any(bDoubleSided) // DoubleSided + }; + + //NormalsKind + if (bFlatNormals) + lcl_addProperty(aPropertyNames, aPropertyValues, + UNO_NAME_3D_NORMALS_KIND, uno::Any(drawing::NormalsKind_FLAT)); + + xMultiPropertySet->setPropertyValues(aPropertyNames, aPropertyValues); + + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + if (xSourceProp.is() && xPropertySet.is()) + { + PropertyMapper::setMappedProperties(xPropertySet, xSourceProp, rPropertyNameMap); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createArea3D( const uno::Reference< drawing::XShapes >& xTarget + , const drawing::PolyPolygonShape3D& rPolyPolygon + , double fDepth ) +{ + if( !xTarget.is() ) + return nullptr; + + if( !rPolyPolygon.SequenceX.hasElements()) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.Shape3DExtrudeObject" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set properties + uno::Reference<beans::XMultiPropertySet> xMultiPropertySet(xShape, uno::UNO_QUERY); + OSL_ENSURE(xMultiPropertySet.is(), "created shape offers no XMultiPropertySet"); + if (xMultiPropertySet.is()) + { + try + { + uno::Sequence<OUString> aPropertyNames{ + UNO_NAME_3D_EXTRUDE_DEPTH, + UNO_NAME_3D_PERCENT_DIAGONAL, + UNO_NAME_3D_POLYPOLYGON3D, + UNO_NAME_3D_DOUBLE_SIDED, + }; + + uno::Sequence<uno::Any> aPropertyValues { + uno::Any(sal_Int32(fDepth)), // depth + uno::Any(sal_Int16(0)), // PercentDiagonal + uno::Any(rPolyPolygon), // Polygon + uno::Any(true) // DoubleSided + }; + + //the z component of the polygon is now ignored by the drawing layer, + //so we need to translate the object via transformation matrix + + //Matrix for position + if (rPolyPolygon.SequenceZ.hasElements()&& rPolyPolygon.SequenceZ[0].hasElements()) + { + basegfx::B3DHomMatrix aM; + aM.translate(0, 0, rPolyPolygon.SequenceZ[0][0]); + drawing::HomogenMatrix aHM = B3DHomMatrixToHomogenMatrix(aM); + lcl_addProperty(aPropertyNames, aPropertyValues, UNO_NAME_3D_TRANSFORM_MATRIX, uno::Any(aHM)); + } + xMultiPropertySet->setPropertyValues(aPropertyNames, aPropertyValues); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createArea2D( const uno::Reference< drawing::XShapes >& xTarget + , const drawing::PolyPolygonShape3D& rPolyPolygon ) +{ + if( !xTarget.is() ) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.PolyPolygonShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set properties + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + //UNO_NAME_POLYGON "Polygon" drawing::PointSequence* + drawing::PointSequenceSequence aPoints( PolyToPointSequence(rPolyPolygon) ); + + //Polygon + xProp->setPropertyValue( UNO_NAME_POLYPOLYGON + , uno::Any( aPoints ) ); + + //ZOrder + //an area should always be behind other shapes + xProp->setPropertyValue( UNO_NAME_MISC_OBJ_ZORDER + , uno::Any( sal_Int32(0) ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +static drawing::PolyPolygonShape3D createPolyPolygon_Symbol( const drawing::Position3D& rPos + , const drawing::Direction3D& rSize + , sal_Int32 nStandardSymbol ) +{ + if(nStandardSymbol<0) + nStandardSymbol*=-1; + nStandardSymbol = nStandardSymbol%ShapeFactory::getSymbolCount(); + SymbolEnum eSymbolType=static_cast<SymbolEnum>(nStandardSymbol); + + const double& fX = rPos.PositionX; + const double& fY = rPos.PositionY; + + const double fWidthH = rSize.DirectionX/2.0; //fWidthH stands for Half Width + const double fHeightH = rSize.DirectionY/2.0; //fHeightH stands for Half Height + + const sal_Int32 nQuarterCount = 35; // points inside a quadrant, used in case circle + + sal_Int32 nPointCount = 4; //all arrow symbols only need 4 points + switch( eSymbolType ) + { + case Symbol_Square: + case Symbol_Diamond: + case Symbol_Bowtie: + case Symbol_Sandglass: + case Symbol_HorizontalBar: + case Symbol_VerticalBar: + nPointCount = 5; + break; + case Symbol_X: + nPointCount = 13; + break; + case Symbol_Plus: + nPointCount = 13; + break; + case Symbol_Star: + nPointCount = 9; + break; + case Symbol_Asterisk: + nPointCount = 19; + break; + case Symbol_Circle: + nPointCount = 5 + 4 * nQuarterCount; + break; + default: + break; + } + + drawing::PolyPolygonShape3D aPP; + + aPP.SequenceX.realloc(1); + aPP.SequenceY.realloc(1); + aPP.SequenceZ.realloc(1); + + drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray(); + + pOuterSequenceX->realloc(nPointCount); + pOuterSequenceY->realloc(nPointCount); + pOuterSequenceZ->realloc(nPointCount); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + for(sal_Int32 nN = nPointCount; nN--;) + *pInnerSequenceZ++ = 0.0; + + switch(eSymbolType) + { + case Symbol_Square: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + break; + } + case Symbol_UpArrow: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + break; + } + case Symbol_DownArrow: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + break; + } + case Symbol_RightArrow: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + break; + } + case Symbol_LeftArrow: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY; + break; + } + case Symbol_Bowtie: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + break; + } + case Symbol_Sandglass: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + break; + } + case Symbol_Diamond: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY; + break; + } + case Symbol_HorizontalBar: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-0.2*fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-0.2*fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+0.2*fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+0.2*fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-0.2*fHeightH; + break; + } + case Symbol_VerticalBar: + { + *pInnerSequenceX++ = fX-0.2*fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX+0.2*fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX+0.2*fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX-0.2*fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX-0.2*fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + break; + } + case Symbol_Circle: + { + double fOmega = 1.5707963267948966192 / (nQuarterCount + 1.0); + // one point in the middle of each edge to get full size bounding rectangle + *pInnerSequenceX++ = fX + fWidthH; + *pInnerSequenceY++ = fY; + // 0 to PI/2 + for (sal_Int32 i = 1; i <= nQuarterCount; ++i) + { + *pInnerSequenceX++ = fX + fWidthH * cos( i * fOmega ); + *pInnerSequenceY++ = fY - fHeightH * sin( i * fOmega ); + } + // PI/2 to PI + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY - fHeightH; + for (sal_Int32 i = 1; i <= nQuarterCount; ++i) + { + *pInnerSequenceX++ = fX - fWidthH * sin( i * fOmega); + *pInnerSequenceY++ = fY - fHeightH * cos( i * fOmega); + } + // PI to 3/2*PI + *pInnerSequenceX++ = fX - fWidthH; + *pInnerSequenceY++ = fY; + for (sal_Int32 i = 1; i <= nQuarterCount; ++i) + { + *pInnerSequenceX++ = fX - fWidthH * cos( i * fOmega); + *pInnerSequenceY++ = fY + fHeightH * sin( i * fOmega); + } + // 3/2*PI to 2*PI + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY + fHeightH; + for (sal_Int32 i = 1; i <= nQuarterCount; ++i) + { + *pInnerSequenceX++ = fX + fWidthH * sin(i * fOmega); + *pInnerSequenceY++ = fY + fHeightH * cos(i * fOmega); + } + // close polygon + *pInnerSequenceX++ = fX + fWidthH; + *pInnerSequenceY++ = fY; + break; + } + case Symbol_Star: + { + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX+0.2*fWidthH; + *pInnerSequenceY++ = fY-0.2*fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX+0.2*fWidthH; + *pInnerSequenceY++ = fY+0.2*fHeightH; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX-0.2*fWidthH; + *pInnerSequenceY++ = fY+0.2*fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX-0.2*fWidthH; + *pInnerSequenceY++ = fY-0.2*fHeightH; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY-fHeightH; + break; + } + case Symbol_X: + { + const double fScaleX = fWidthH / 128.0; + const double fScaleY = fHeightH / 128.0; + const double fSmall = sqrt(200.0); + const double fLarge = 128.0 - fSmall; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY - fScaleY * fSmall; + + *pInnerSequenceX++ = fX - fScaleX * fLarge; + *pInnerSequenceY++ = fY - fHeightH; + + *pInnerSequenceX++ = fX - fWidthH; + *pInnerSequenceY++ = fY - fScaleY * fLarge; + + *pInnerSequenceX++ = fX - fScaleX * fSmall; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX - fWidthH; + *pInnerSequenceY++ = fY + fScaleY * fLarge; + + *pInnerSequenceX++ = fX - fScaleX * fLarge; + *pInnerSequenceY++ = fY + fHeightH; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY + fScaleY * fSmall; + + *pInnerSequenceX++ = fX + fScaleX * fLarge; + *pInnerSequenceY++ = fY + fHeightH; + + *pInnerSequenceX++ = fX + fWidthH; + *pInnerSequenceY++ = fY + fScaleY * fLarge; + + *pInnerSequenceX++ = fX + fScaleX * fSmall; + *pInnerSequenceY++ = fY; + + *pInnerSequenceX++ = fX + fWidthH; + *pInnerSequenceY++ = fY - fScaleY * fLarge; + + *pInnerSequenceX++ = fX + fScaleX * fLarge; + *pInnerSequenceY++ = fY - fHeightH; + + *pInnerSequenceX++ = fX; + *pInnerSequenceY++ = fY - fScaleY * fSmall; + break; + + } + case Symbol_Plus: + { + const double fScaleX = fWidthH / 128.0; + const double fScaleY = fHeightH / 128.0; + const double fHalf = 10.0; //half line width on 256 size square + const double fdX = fScaleX * fHalf; + const double fdY = fScaleY * fHalf; + + *pInnerSequenceX++ = fX-fdX; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fdX; + *pInnerSequenceY++ = fY-fdY; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fdY; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fdY; + + *pInnerSequenceX++ = fX-fdX; + *pInnerSequenceY++ = fY+fdY; + + *pInnerSequenceX++ = fX-fdX; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fdX; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fdX; + *pInnerSequenceY++ = fY+fdY; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fdY; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fdY; + + *pInnerSequenceX++ = fX+fdX; + *pInnerSequenceY++ = fY-fdY; + + *pInnerSequenceX++ = fX+fdY; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fdX; + *pInnerSequenceY++ = fY-fHeightH; + break; + + } + case Symbol_Asterisk: + { + const double fHalf = 10.0; // half line width on 256 size square + const double fTwoY = fHalf * sqrt(3.0); + const double fFourY = (128.0 - 2.0 * fHalf ) / sqrt(3.0); + const double fThreeX = 128.0 - fHalf; + const double fThreeY = fHalf * sqrt(3.0) + fFourY; + const double fFiveX = 2.0 * fHalf; + + const double fScaleX = fWidthH / 128.0; + const double fScaleY = fHeightH / 128.0; + + //1 + *pInnerSequenceX++ = fX-fScaleX * fHalf; + *pInnerSequenceY++ = fY-fHeightH; + //2 + *pInnerSequenceX++ = fX-fScaleX * fHalf; + *pInnerSequenceY++ = fY-fScaleY * fTwoY; + //3 + *pInnerSequenceX++ = fX-fScaleX * fThreeX; + *pInnerSequenceY++ = fY-fScaleY * fThreeY; + //4 + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fScaleY * fFourY; + //5 + *pInnerSequenceX++ = fX-fScaleX * fFiveX; + *pInnerSequenceY++ = fY; + //6 as 4 + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fScaleY * fFourY; + //7 as 3 + *pInnerSequenceX++ = fX-fScaleX * fThreeX; + *pInnerSequenceY++ = fY+fScaleY * fThreeY; + //8 as 2 + *pInnerSequenceX++ = fX-fScaleX * fHalf; + *pInnerSequenceY++ = fY+fScaleY * fTwoY; + //9 as 1 + *pInnerSequenceX++ = fX-fScaleX * fHalf; + *pInnerSequenceY++ = fY+fHeightH; + //10 as 1 + *pInnerSequenceX++ = fX+fScaleX * fHalf; + *pInnerSequenceY++ = fY+fHeightH; + //11 as 2 + *pInnerSequenceX++ = fX+fScaleX * fHalf; + *pInnerSequenceY++ = fY+fScaleY * fTwoY; + //12 as 3 + *pInnerSequenceX++ = fX+fScaleX * fThreeX; + *pInnerSequenceY++ = fY+fScaleY * fThreeY; + //13 as 4 + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fScaleY * fFourY; + //14 as 5 + *pInnerSequenceX++ = fX+fScaleX * fFiveX; + *pInnerSequenceY++ = fY; + //15 as 4 + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fScaleY * fFourY; + //16 as 3 + *pInnerSequenceX++ = fX+fScaleX * fThreeX; + *pInnerSequenceY++ = fY-fScaleY * fThreeY; + //17 as 2 + *pInnerSequenceX++ = fX+fScaleX * fHalf; + *pInnerSequenceY++ = fY-fScaleY * fTwoY; + // 18 as 1 + *pInnerSequenceX++ = fX+fScaleX * fHalf; + *pInnerSequenceY++ = fY-fHeightH; + // 19 = 1, closing + *pInnerSequenceX++ = fX-fScaleX * fHalf; + *pInnerSequenceY++ = fY-fHeightH; + break; + } + default: //case Symbol_Square: + { + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY+fHeightH; + + *pInnerSequenceX++ = fX+fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + + *pInnerSequenceX++ = fX-fWidthH; + *pInnerSequenceY++ = fY-fHeightH; + break; + } + } + + return aPP; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createSymbol2D( + const uno::Reference< drawing::XShapes >& xTarget + , const drawing::Position3D& rPosition + , const drawing::Direction3D& rSize + , sal_Int32 nStandardSymbol + , sal_Int32 nBorderColor + , sal_Int32 nFillColor ) +{ + if( !xTarget.is() ) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.PolyPolygonShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set properties + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + drawing::PointSequenceSequence aPoints( PolyToPointSequence( + createPolyPolygon_Symbol( rPosition, rSize, nStandardSymbol ) )); + + //Polygon + xProp->setPropertyValue( UNO_NAME_POLYPOLYGON + , uno::Any( aPoints ) ); + + //LineColor + xProp->setPropertyValue( UNO_NAME_LINECOLOR + , uno::Any( nBorderColor ) ); + + //FillColor + xProp->setPropertyValue( UNO_NAME_FILLCOLOR + , uno::Any( nFillColor ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createGraphic2D( + const uno::Reference< drawing::XShapes >& xTarget + , const drawing::Position3D& rPosition + , const drawing::Direction3D& rSize + , const uno::Reference< graphic::XGraphic >& xGraphic ) +{ + if( !xTarget.is() || !xGraphic.is() ) + return nullptr; + + // @todo: change this to a rectangle shape with a fill bitmap for + // performance reasons (ask AW, said CL) + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.GraphicObjectShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + try + { + // assume position is upper left corner. Transform to center. + drawing::Position3D aCenterPosition( + rPosition.PositionX - (rSize.DirectionX / 2.0), + rPosition.PositionY - (rSize.DirectionY / 2.0), + rPosition.PositionZ ); + xShape->setPosition( Position3DToAWTPoint( aCenterPosition )); + xShape->setSize( Direction3DToAWTSize( rSize )); + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + xProp->setPropertyValue( "Graphic", uno::Any( xGraphic )); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShapes > + ShapeFactory::createGroup2D( const uno::Reference< drawing::XShapes >& xTarget + , const OUString& aName ) +{ + if( !xTarget.is() ) + return nullptr; + try + { + //create and add to target + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.GroupShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set name + if(!aName.isEmpty()) + setShapeName( xShape , aName ); + + {//workaround + //need this null size as otherwise empty group shapes where painted with a gray border + xShape->setSize(awt::Size(0,0)); + } + + //return + uno::Reference< drawing::XShapes > xShapes( xShape, uno::UNO_QUERY ); + return xShapes; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return nullptr; +} + +uno::Reference< drawing::XShapes > + ShapeFactory::createGroup3D( const uno::Reference< drawing::XShapes >& xTarget + , const OUString& aName ) +{ + if( !xTarget.is() ) + return nullptr; + try + { + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.Shape3DSceneObject" ), uno::UNO_QUERY ); + + xTarget->add(xShape); + + //it is necessary to set the transform matrix to initialize the scene properly + //otherwise all objects which are placed into this Group will not be visible + //the following should be unnecessary after the bug is fixed + { + //set properties + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + ::basegfx::B3DHomMatrix aM; + xProp->setPropertyValue( UNO_NAME_3D_TRANSFORM_MATRIX + , uno::Any(B3DHomMatrixToHomogenMatrix(aM)) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } + + //set name + if(!aName.isEmpty()) + setShapeName( xShape , aName ); + + //return + uno::Reference< drawing::XShapes > xShapes( xShape, uno::UNO_QUERY ); + return xShapes; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return nullptr; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createCircle2D( const uno::Reference< drawing::XShapes >& xTarget + , const drawing::Position3D& rPosition + , const drawing::Direction3D& rSize ) +{ + if( !xTarget.is() ) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.EllipseShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + try + { + drawing::Position3D aCenterPosition( + rPosition.PositionX - (rSize.DirectionX / 2.0), + rPosition.PositionY - (rSize.DirectionY / 2.0), + rPosition.PositionZ ); + xShape->setPosition( Position3DToAWTPoint( aCenterPosition )); + xShape->setSize( Direction3DToAWTSize( rSize )); + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + + //set properties + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + xProp->setPropertyValue( UNO_NAME_CIRCKIND, uno::Any( drawing::CircleKind_FULL ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createCircle( const uno::Reference< drawing::XShapes >& xTarget + , const awt::Size& rSize + , const awt::Point& rPosition ) +{ + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.EllipseShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + xShape->setSize( rSize ); + xShape->setPosition( rPosition ); + + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createLine3D( const uno::Reference< drawing::XShapes >& xTarget + , const drawing::PolyPolygonShape3D& rPoints + , const VLineProperties& rLineProperties ) +{ + if( !xTarget.is() ) + return nullptr; + + if(!rPoints.SequenceX.hasElements()) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.Shape3DPolygonObject" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set properties + uno::Reference<beans::XMultiPropertySet> xMultiPropertySet(xShape, uno::UNO_QUERY); + OSL_ENSURE(xMultiPropertySet.is(), "created shape offers no XMultiPropertySet"); + if (xMultiPropertySet.is()) + { + try + { + uno::Sequence<OUString> aPropertyNames { + UNO_NAME_3D_POLYPOLYGON3D, + UNO_NAME_3D_LINEONLY + }; + + uno::Sequence<uno::Any> aPropertyValues { + uno::Any(rPoints), // Polygon + uno::Any(true) // LineOnly + }; + + //Transparency + if(rLineProperties.Transparence.hasValue()) + { + lcl_addProperty(aPropertyNames, aPropertyValues, + UNO_NAME_LINETRANSPARENCE, + rLineProperties.Transparence); + } + + //LineStyle + if(rLineProperties.LineStyle.hasValue()) + { + lcl_addProperty(aPropertyNames, aPropertyValues, + UNO_NAME_LINESTYLE, + rLineProperties.LineStyle); + } + + //LineWidth + if(rLineProperties.Width.hasValue()) + { + lcl_addProperty(aPropertyNames, aPropertyValues, + UNO_NAME_LINEWIDTH, + rLineProperties.Width); + } + + //LineColor + if(rLineProperties.Color.hasValue()) + { + lcl_addProperty(aPropertyNames, aPropertyValues, + UNO_NAME_LINECOLOR, + rLineProperties.Color); + } + xMultiPropertySet->setPropertyValues(aPropertyNames, aPropertyValues); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createLine2D( const uno::Reference< drawing::XShapes >& xTarget + , const drawing::PointSequenceSequence& rPoints + , const VLineProperties* pLineProperties ) +{ + if( !xTarget.is() ) + return nullptr; + + if(!rPoints.hasElements()) + return nullptr; + + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.PolyLineShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set properties + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "created shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + //Polygon + xProp->setPropertyValue( UNO_NAME_POLYPOLYGON + , uno::Any( rPoints ) ); + + if(pLineProperties) + { + //Transparency + if(pLineProperties->Transparence.hasValue()) + xProp->setPropertyValue( UNO_NAME_LINETRANSPARENCE + , pLineProperties->Transparence ); + + //LineStyle + if(pLineProperties->LineStyle.hasValue()) + xProp->setPropertyValue( UNO_NAME_LINESTYLE + , pLineProperties->LineStyle ); + + //LineWidth + if(pLineProperties->Width.hasValue()) + xProp->setPropertyValue( UNO_NAME_LINEWIDTH + , pLineProperties->Width ); + + //LineColor + if(pLineProperties->Color.hasValue()) + xProp->setPropertyValue( UNO_NAME_LINECOLOR + , pLineProperties->Color ); + + //LineDashName + if(pLineProperties->DashName.hasValue()) + xProp->setPropertyValue( "LineDashName" + , pLineProperties->DashName ); + + //LineCap + if(pLineProperties->LineCap.hasValue()) + xProp->setPropertyValue( UNO_NAME_LINECAP + , pLineProperties->LineCap ); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createLine ( const uno::Reference< drawing::XShapes >& xTarget, + const awt::Size& rSize, const awt::Point& rPosition ) +{ + //create shape + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.LineShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + xShape->setSize( rSize ); + xShape->setPosition( rPosition ); + + return xShape; +} + +uno::Reference< drawing::XShape > ShapeFactory::createInvisibleRectangle( + const uno::Reference< drawing::XShapes >& xTarget + , const awt::Size& rSize ) +{ + try + { + if(!xTarget.is()) + return nullptr; + + uno::Reference< drawing::XShape > xShape( m_xShapeFactory->createInstance( + "com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY ); + if( xShape.is()) + { + xTarget->add( xShape ); + ShapeFactory::makeShapeInvisible( xShape ); + xShape->setSize( rSize ); + } + return xShape; + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + return nullptr; +} + +uno::Reference< drawing::XShape > ShapeFactory::createRectangle( + const uno::Reference< drawing::XShapes >& xTarget, + const awt::Size& rSize, + const awt::Point& rPosition, + const tNameSequence& rPropNames, + const tAnySequence& rPropValues, + StackPosition ePos ) +{ + uno::Reference< drawing::XShape > xShape( m_xShapeFactory->createInstance( + "com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY ); + if( xShape.is()) + { + if (ePos == StackPosition::Bottom) + { + uno::Reference<drawing::XShapes2> xTarget2(xTarget, uno::UNO_QUERY); + if (xTarget2.is()) + xTarget2->addBottom(xShape); + } + else + xTarget->add(xShape); + + xShape->setPosition( rPosition ); + xShape->setSize( rSize ); + uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY_THROW ); + PropertyMapper::setMultiProperties( rPropNames, rPropValues, xPropSet ); + } + + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createRectangle( + const uno::Reference< + drawing::XShapes >& xTarget ) +{ + uno::Reference< drawing::XShape > xShape( m_xShapeFactory->createInstance( + "com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY ); + xTarget->add( xShape ); + + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createText( const uno::Reference< drawing::XShapes >& xTarget + , const OUString& rText + , const tNameSequence& rPropNames + , const tAnySequence& rPropValues + , const uno::Any& rATransformation ) +{ + if( !xTarget.is() ) + return nullptr; + + if(rText.isEmpty()) + return nullptr; + + //create shape and add to page + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.TextShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set text + uno::Reference< text::XTextRange > xTextRange( xShape, uno::UNO_QUERY ); + if( xTextRange.is() ) + xTextRange->setString( rText ); + + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + if( xProp.is() ) + { + //set properties + PropertyMapper::setMultiProperties( rPropNames, rPropValues, xProp ); + + //set position matrix + //the matrix needs to be set at the end behind autogrow and such position influencing properties + try + { + if (rATransformation.hasValue()) + xProp->setPropertyValue( "Transformation", rATransformation ); + else + SAL_INFO("chart2", "No rATransformation value is given to ShapeFactory::createText()"); + + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createText( const uno::Reference< drawing::XShapes >& xTarget + , const uno::Sequence< OUString >& rTextParagraphs + , const uno::Sequence< tNameSequence >& rParaPropNames + , const uno::Sequence< tAnySequence >& rParaPropValues + , const tNameSequence& rPropNames + , const tAnySequence& rPropValues + , const uno::Any& rATransformation ) +{ + if( !xTarget.is() ) + return nullptr; + + if( !rTextParagraphs.hasElements() ) + return nullptr; + + sal_Int32 nNumberOfParagraphs = rTextParagraphs.getLength(); + + if( rParaPropNames.getLength() != nNumberOfParagraphs ) + return nullptr; + + if( rParaPropValues.getLength() != nNumberOfParagraphs ) + return nullptr; + + bool bNotEmpty = false; + for( sal_Int32 nN = 0; nN < nNumberOfParagraphs; ++nN ) + { + if( !rTextParagraphs[nN].isEmpty() ) + { + bNotEmpty = true; + break; + } + } + if( !bNotEmpty ) + return nullptr; + + //create shape and add to page + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.TextShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set paragraph properties + bNotEmpty = false; + Reference< text::XText > xText( xShape, uno::UNO_QUERY ); + if( xText.is() ) + { + // the first cursor is used for appending the next paragraph, + // after a new string has been inserted the cursor is moved at the end + // of the inserted string + // the second cursor is used for selecting the paragraph and apply the + // passed text properties + Reference< text::XTextCursor > xInsertCursor = xText->createTextCursor(); + Reference< text::XTextCursor > xSelectionCursor = xText->createTextCursor(); + if( xInsertCursor.is() && xSelectionCursor.is() ) + { + uno::Reference< beans::XPropertySet > xSelectionProp( xSelectionCursor, uno::UNO_QUERY ); + if( xSelectionProp.is() ) + { + for( sal_Int32 nN = 0; nN < nNumberOfParagraphs; ++nN ) + { + if( !rTextParagraphs[nN].isEmpty() ) + { + xInsertCursor->gotoEnd(false); + if( bNotEmpty ) + { + xText->insertString( xInsertCursor, "\n", false ); + } + xSelectionCursor->gotoEnd(false); + xText->insertString( xInsertCursor, rTextParagraphs[nN], false ); + bNotEmpty = true; + xSelectionCursor->gotoEnd(true); // select current paragraph + PropertyMapper::setMultiProperties( rParaPropNames[nN], rParaPropValues[nN], xSelectionProp ); + } + } + } + } + } + + if( !bNotEmpty ) + return nullptr; + + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + if( xProp.is() ) + { + //set whole text shape properties + PropertyMapper::setMultiProperties( rPropNames, rPropValues, xProp ); + + if (rATransformation.hasValue()) + { + //set position matrix + //the matrix needs to be set at the end behind autogrow and such position influencing properties + try + { + xProp->setPropertyValue( "Transformation", rATransformation ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createText( const uno::Reference< drawing::XShapes >& xTarget + , uno::Sequence< uno::Reference< chart2::XFormattedString > >& xFormattedString + , const tNameSequence& rPropNames + , const tAnySequence& rPropValues + , const uno::Any& rATransformation ) +{ + if( !xTarget.is() ) + return nullptr; + + if( !xFormattedString.hasElements() ) + return nullptr; + + sal_Int32 nNumberOfParagraphs = xFormattedString.getLength(); + + bool bNotEmpty = false; + for( sal_Int32 nN = 0; nN < nNumberOfParagraphs; ++nN ) + { + if( !xFormattedString[nN]->getString().isEmpty() ) + { + bNotEmpty = true; + break; + } + } + if( !bNotEmpty ) + return nullptr; + + //create shape and add to page + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.TextShape" ), uno::UNO_QUERY ); + xTarget->add(xShape); + + //set paragraph properties + bNotEmpty = false; + Reference< text::XText > xText( xShape, uno::UNO_QUERY ); + if( xText.is() ) + { + // the first cursor is used for appending the next paragraph, + // after a new string has been inserted the cursor is moved at the end + // of the inserted string + // the second cursor is used for selecting the paragraph and apply the + // passed text properties + Reference< text::XTextCursor > xInsertCursor = xText->createTextCursor(); + Reference< text::XTextCursor > xSelectionCursor = xText->createTextCursor(); + if( xInsertCursor.is() && xSelectionCursor.is() ) + { + uno::Reference< beans::XPropertySet > xSelectionProp( xSelectionCursor, uno::UNO_QUERY ); + if( xSelectionProp.is() ) + { + for( sal_Int32 nN = 0; nN < nNumberOfParagraphs; ++nN ) + { + if( !xFormattedString[nN]->getString().isEmpty() ) + { + xInsertCursor->gotoEnd( false ); + xSelectionCursor->gotoEnd( false ); + xText->insertString( xInsertCursor, xFormattedString[nN]->getString(), false ); + bNotEmpty = true; + xSelectionCursor->gotoEnd( true ); // select current paragraph + uno::Reference< beans::XPropertySet > xStringProperties( xFormattedString[nN], uno::UNO_QUERY ); + PropertyMapper::setMappedProperties( xSelectionProp, xStringProperties, + PropertyMapper::getPropertyNameMapForTextShapeProperties() ); + } + } + } + } + } + + if( !bNotEmpty ) + return nullptr; + + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + if( xProp.is() ) + { + //set whole text shape properties + PropertyMapper::setMultiProperties( rPropNames, rPropValues, xProp ); + + if( rATransformation.hasValue() ) + { + //set position matrix + //the matrix needs to be set at the end behind autogrow and such position influencing properties + try + { + xProp->setPropertyValue( "Transformation", rATransformation ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } + return xShape; +} + +uno::Reference< drawing::XShape > + ShapeFactory::createText( const uno::Reference< drawing::XShapes >& xTarget, + const awt::Size& rSize, + const awt::Point& rPos, + uno::Sequence< uno::Reference< chart2::XFormattedString > >& xFormattedString, + const uno::Reference< + beans::XPropertySet > & xTextProperties, + double nRotation, const OUString& aName, sal_Int32 nTextMaxWidth ) +{ + //create shape and add to page + uno::Reference< drawing::XShape > xShape( + m_xShapeFactory->createInstance( + "com.sun.star.drawing.TextShape" ), uno::UNO_QUERY ); + try + { + xTarget->add(xShape); + + //set text and text properties + uno::Reference< text::XText > xText( xShape, uno::UNO_QUERY ); + uno::Reference< text::XTextCursor > xTextCursor( xText->createTextCursor() ); + uno::Reference< beans::XPropertySet > xShapeProp( xShape, uno::UNO_QUERY ); + if( !xText.is() || !xTextCursor.is() || !xShapeProp.is() || !xTextProperties.is() ) + return xShape; + + tPropertyNameValueMap aValueMap; + //fill line-, fill- and paragraph-properties into the ValueMap + { + tPropertyNameMap aNameMap = PropertyMapper::getPropertyNameMapForParagraphProperties(); + auto const & add = PropertyMapper::getPropertyNameMapForFillAndLineProperties(); + aNameMap.insert(add.begin(), add.end()); + + PropertyMapper::getValueMap( aValueMap, aNameMap, xTextProperties ); + } + + //fill some more shape properties into the ValueMap + { + aValueMap.insert( { "TextHorizontalAdjust", uno::Any(drawing::TextHorizontalAdjust_CENTER) } ); // drawing::TextHorizontalAdjust + aValueMap.insert( { "TextVerticalAdjust", uno::Any(drawing::TextVerticalAdjust_CENTER) } ); //drawing::TextVerticalAdjust + aValueMap.insert( { "TextAutoGrowHeight", uno::Any(true) } ); // sal_Bool + aValueMap.insert( { "TextAutoGrowWidth", uno::Any(true) } ); // sal_Bool + aValueMap.insert( { "TextMaximumFrameWidth", uno::Any(nTextMaxWidth) } ); // sal_Int32 + + //set name/classified ObjectID (CID) + if( !aName.isEmpty() ) + aValueMap.emplace( "Name", uno::Any( aName ) ); //CID OUString + } + + //set global title properties + { + tNameSequence aPropNames; + tAnySequence aPropValues; + PropertyMapper::getMultiPropertyListsFromValueMap( aPropNames, aPropValues, aValueMap ); + PropertyMapper::setMultiProperties( aPropNames, aPropValues, xShapeProp ); + } + + bool bStackCharacters(false); + try + { + xTextProperties->getPropertyValue( "StackCharacters" ) >>= bStackCharacters; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + + if(bStackCharacters) + { + //if the characters should be stacked we use only the first character properties for code simplicity + if( xFormattedString.hasElements() ) + { + OUString aLabel; + for( const auto & i : std::as_const(xFormattedString) ) + aLabel += i->getString(); + aLabel = ShapeFactory::getStackedString( aLabel, bStackCharacters ); + + xTextCursor->gotoEnd(false); + xText->insertString( xTextCursor, aLabel, false ); + xTextCursor->gotoEnd(true); + uno::Reference< beans::XPropertySet > xTargetProps( xShape, uno::UNO_QUERY ); + uno::Reference< beans::XPropertySet > xSourceProps( xFormattedString[0], uno::UNO_QUERY ); + + PropertyMapper::setMappedProperties( xTargetProps, xSourceProps + , PropertyMapper::getPropertyNameMapForCharacterProperties() ); + + // adapt font size according to page size + awt::Size aOldRefSize; + if( xTextProperties->getPropertyValue( "ReferencePageSize") >>= aOldRefSize ) + { + RelativeSizeHelper::adaptFontSizes( xTargetProps, aOldRefSize, rSize ); + } + } + } + else + { + for( const uno::Reference< chart2::XFormattedString >& rxFS : std::as_const(xFormattedString) ) + { + xTextCursor->gotoEnd(false); + xText->insertString( xTextCursor, rxFS->getString(), false ); + xTextCursor->gotoEnd(true); + } + awt::Size aOldRefSize; + bool bHasRefPageSize = + ( xTextProperties->getPropertyValue( "ReferencePageSize") >>= aOldRefSize ); + + if( xFormattedString.hasElements() ) + { + uno::Reference< beans::XPropertySet > xTargetProps( xShape, uno::UNO_QUERY ); + uno::Reference< beans::XPropertySet > xSourceProps( xFormattedString[0], uno::UNO_QUERY ); + PropertyMapper::setMappedProperties( xTargetProps, xSourceProps, PropertyMapper::getPropertyNameMapForCharacterProperties() ); + + // adapt font size according to page size + if( bHasRefPageSize ) + { + RelativeSizeHelper::adaptFontSizes( xTargetProps, aOldRefSize, rSize ); + } + } + } + + // #i109336# Improve auto positioning in chart + float fFontHeight = 0.0; + if ( xShapeProp.is() && ( xShapeProp->getPropertyValue( "CharHeight" ) >>= fFontHeight ) ) + { + fFontHeight *= ( 2540.0f / 72.0f ); // pt -> 1/100 mm + sal_Int32 nXDistance = static_cast< sal_Int32 >( ::rtl::math::round( fFontHeight * 0.18f ) ); + sal_Int32 nYDistance = static_cast< sal_Int32 >( ::rtl::math::round( fFontHeight * 0.30f ) ); + xShapeProp->setPropertyValue( "TextLeftDistance", uno::Any( nXDistance ) ); + xShapeProp->setPropertyValue( "TextRightDistance", uno::Any( nXDistance ) ); + xShapeProp->setPropertyValue( "TextUpperDistance", uno::Any( nYDistance ) ); + xShapeProp->setPropertyValue( "TextLowerDistance", uno::Any( nYDistance ) ); + } + sal_Int32 nXPos = rPos.X; + sal_Int32 nYPos = rPos.Y; + + //set position matrix + //the matrix needs to be set at the end behind autogrow and such position influencing properties + ::basegfx::B2DHomMatrix aM; + aM.rotate( -basegfx::deg2rad(nRotation) );//#i78696#->#i80521# + aM.translate( nXPos, nYPos ); + xShapeProp->setPropertyValue( "Transformation", uno::Any( B2DHomMatrixToHomogenMatrix3(aM) ) ); + + xShapeProp->setPropertyValue( "ParaAdjust", uno::Any( style::ParagraphAdjust_CENTER ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return xShape; +} + +ShapeFactory* ShapeFactory::getOrCreateShapeFactory(const uno::Reference< lang::XMultiServiceFactory>& xFactory) +{ + static ShapeFactory* pShapeFactory = new ShapeFactory(xFactory); + return pShapeFactory; +} + +uno::Reference< drawing::XShapes > ShapeFactory::getChartRootShape( + const uno::Reference< drawing::XDrawPage>& xDrawPage ) +{ + uno::Reference< drawing::XShapes > xRet; + const uno::Reference< drawing::XShapes > xShapes = xDrawPage; + if( xShapes.is() ) + { + sal_Int32 nCount = xShapes->getCount(); + uno::Reference< drawing::XShape > xShape; + for( sal_Int32 nN = nCount; nN--; ) + { + if( xShapes->getByIndex( nN ) >>= xShape ) + { + if( ShapeFactory::getShapeName( xShape ) == "com.sun.star.chart2.shapes" ) + { + xRet.set( xShape, uno::UNO_QUERY ); + break; + } + } + } + } + return xRet; +} + +void ShapeFactory::makeShapeInvisible( const uno::Reference< drawing::XShape >& xShape ) +{ + uno::Reference< beans::XPropertySet > xShapeProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xShapeProp.is(), "created shape offers no XPropertySet"); + if( xShapeProp.is()) + { + try + { + xShapeProp->setPropertyValue( "LineStyle", uno::Any( drawing::LineStyle_NONE )); + xShapeProp->setPropertyValue( "FillStyle", uno::Any( drawing::FillStyle_NONE )); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } +} + +// set a name/CID at a shape (is used for selection handling) + +void ShapeFactory::setShapeName( const uno::Reference< drawing::XShape >& xShape + , const OUString& rName ) +{ + if(!xShape.is()) + return; + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + xProp->setPropertyValue( UNO_NAME_MISC_OBJ_NAME + , uno::Any( rName ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } +} + +OUString ShapeFactory::getShapeName( const uno::Reference< drawing::XShape >& xShape ) +{ + OUString aRet; + + uno::Reference< beans::XPropertySet > xProp( xShape, uno::UNO_QUERY ); + OSL_ENSURE(xProp.is(), "shape offers no XPropertySet"); + if( xProp.is()) + { + try + { + xProp->getPropertyValue( UNO_NAME_MISC_OBJ_NAME ) >>= aRet; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + + return aRet; +} + +uno::Any ShapeFactory::makeTransformation( const awt::Point& rScreenPosition2D, double fRotationAnglePi ) +{ + ::basegfx::B2DHomMatrix aM; + //As autogrow is active the rectangle is automatically expanded to that side + //to which the text is not adjusted. + // aM.scale( 1, 1 ); Oops? A scale with this parameters is neutral, line commented out + aM.rotate( fRotationAnglePi ); + aM.translate( rScreenPosition2D.X, rScreenPosition2D.Y ); + uno::Any aATransformation( B2DHomMatrixToHomogenMatrix3(aM) ); + return aATransformation; +} + +OUString ShapeFactory::getStackedString( const OUString& rString, bool bStacked ) +{ + sal_Int32 nLen = rString.getLength(); + if(!bStacked || !nLen) + return rString; + + OUStringBuffer aStackStr; + + //add a newline after each letter + //as we do not no letters here add a newline after each char + for( sal_Int32 nPosSrc=0; nPosSrc < nLen; nPosSrc++ ) + { + if( nPosSrc ) + aStackStr.append( '\r' ); + aStackStr.append(rString[nPosSrc]); + } + return aStackStr.makeStringAndClear(); +} + +bool ShapeFactory::hasPolygonAnyLines( drawing::PolyPolygonShape3D& rPoly) +{ + // #i67757# check all contained polygons, if at least one polygon contains 2 or more points, return true + for( auto const & i : std::as_const(rPoly.SequenceX) ) + if( i.getLength() > 1 ) + return true; + return false; +} + +bool ShapeFactory::isPolygonEmptyOrSinglePoint( drawing::PolyPolygonShape3D& rPoly) +{ + // true, if empty polypolygon or one polygon with one point + return !rPoly.SequenceX.hasElements() || + ((rPoly.SequenceX.getLength() == 1) && (rPoly.SequenceX[0].getLength() <= 1)); +} + +void ShapeFactory::closePolygon( drawing::PolyPolygonShape3D& rPoly) +{ + OSL_ENSURE( rPoly.SequenceX.getLength() <= 1, "ShapeFactory::closePolygon - single polygon expected" ); + //add a last point == first point + if(isPolygonEmptyOrSinglePoint(rPoly)) + return; + drawing::Position3D aFirst(rPoly.SequenceX[0][0],rPoly.SequenceY[0][0],rPoly.SequenceZ[0][0]); + AddPointToPoly( rPoly, aFirst ); +} + +awt::Size ShapeFactory::calculateNewSizeRespectingAspectRatio( + const awt::Size& rTargetSize + , const awt::Size& rSourceSizeWithCorrectAspectRatio ) +{ + awt::Size aNewSize; + + double fFactorWidth = double(rTargetSize.Width)/double(rSourceSizeWithCorrectAspectRatio.Width); + double fFactorHeight = double(rTargetSize.Height)/double(rSourceSizeWithCorrectAspectRatio.Height); + double fFactor = std::min(fFactorWidth,fFactorHeight); + aNewSize.Width=static_cast<sal_Int32>(fFactor*rSourceSizeWithCorrectAspectRatio.Width); + aNewSize.Height=static_cast<sal_Int32>(fFactor*rSourceSizeWithCorrectAspectRatio.Height); + + return aNewSize; +} + +awt::Point ShapeFactory::calculateTopLeftPositionToCenterObject( + const awt::Point& rTargetAreaPosition + , const awt::Size& rTargetAreaSize + , const awt::Size& rObjectSize ) +{ + awt::Point aNewPosition(rTargetAreaPosition); + aNewPosition.X += static_cast<sal_Int32>(double(rTargetAreaSize.Width-rObjectSize.Width)/2.0); + aNewPosition.Y += static_cast<sal_Int32>(double(rTargetAreaSize.Height-rObjectSize.Height)/2.0); + return aNewPosition; +} + +::basegfx::B2IRectangle ShapeFactory::getRectangleOfShape( + const uno::Reference< drawing::XShape >& xShape ) +{ + ::basegfx::B2IRectangle aRet; + + if( xShape.is() ) + { + awt::Point aPos = xShape->getPosition(); + awt::Size aSize = xShape->getSize(); + aRet = BaseGFXHelper::makeRectangle(aPos,aSize); + } + return aRet; + +} + +awt::Size ShapeFactory::getSizeAfterRotation( + const uno::Reference< drawing::XShape >& xShape, double fRotationAngleDegree ) +{ + awt::Size aRet(0,0); + if(xShape.is()) + { + const awt::Size aSize( xShape->getSize() ); + + if( fRotationAngleDegree == 0.0 ) + aRet = aSize; + else + { + fRotationAngleDegree = NormAngle360(fRotationAngleDegree); + if(fRotationAngleDegree>270.0) + fRotationAngleDegree=360.0-fRotationAngleDegree; + else if(fRotationAngleDegree>180.0) + fRotationAngleDegree=fRotationAngleDegree-180.0; + else if(fRotationAngleDegree>90.0) + fRotationAngleDegree=180.0-fRotationAngleDegree; + + const double fAnglePi = basegfx::deg2rad(fRotationAngleDegree); + + aRet.Height = static_cast<sal_Int32>( + aSize.Width*rtl::math::sin( fAnglePi ) + + aSize.Height*rtl::math::cos( fAnglePi )); + aRet.Width = static_cast<sal_Int32>( + aSize.Width*rtl::math::cos( fAnglePi ) + + aSize.Height*rtl::math::sin( fAnglePi )); + } + } + return aRet; +} + +void ShapeFactory::removeSubShapes( const uno::Reference< drawing::XShapes >& xShapes ) +{ + if( xShapes.is() ) + { + sal_Int32 nSubCount = xShapes->getCount(); + uno::Reference< drawing::XShape > xShape; + for( sal_Int32 nS = nSubCount; nS--; ) + { + if( xShapes->getByIndex( nS ) >>= xShape ) + xShapes->remove( xShape ); + } + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/Stripe.cxx b/chart2/source/view/main/Stripe.cxx new file mode 100644 index 000000000..74c8ad046 --- /dev/null +++ b/chart2/source/view/main/Stripe.cxx @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <Stripe.hxx> +#include <CommonConverters.hxx> +#include <com/sun/star/drawing/PolyPolygonShape3D.hpp> +#include <com/sun/star/drawing/DoubleSequence.hpp> +#include <basegfx/polygon/b3dpolygon.hxx> + +using namespace ::com::sun::star; + +namespace chart +{ + +Stripe::Stripe( const drawing::Position3D& rPoint1 + , const drawing::Direction3D& rDirectionToPoint2 + , const drawing::Direction3D& rDirectionToPoint4 ) + : m_aPoint1(rPoint1) + , m_aPoint2(rPoint1+rDirectionToPoint2) + , m_aPoint3(m_aPoint2+rDirectionToPoint4) + , m_aPoint4(rPoint1+rDirectionToPoint4) + , m_bInvertNormal(false) + , m_bManualNormalSet(false) +{ +} + +Stripe::Stripe( const drawing::Position3D& rPoint1 + , const drawing::Position3D& rPoint2 + , double fDepth ) + : m_aPoint1(rPoint1) + , m_aPoint2(rPoint2) + , m_aPoint3(rPoint2) + , m_aPoint4(rPoint1) + , m_bInvertNormal(false) + , m_bManualNormalSet(false) +{ + m_aPoint3.PositionZ += fDepth; + m_aPoint4.PositionZ += fDepth; +} + +Stripe::Stripe( const drawing::Position3D& rPoint1 + , const drawing::Position3D& rPoint2 + , const drawing::Position3D& rPoint3 + , const drawing::Position3D& rPoint4 ) + : m_aPoint1(rPoint1) + , m_aPoint2(rPoint2) + , m_aPoint3(rPoint3) + , m_aPoint4(rPoint4) + , m_bInvertNormal(false) + , m_bManualNormalSet(false) +{ +} + +void Stripe::SetManualNormal( const drawing::Direction3D& rNormal ) +{ + m_aManualNormal = rNormal; + m_bManualNormalSet = true; +} + +void Stripe::InvertNormal( bool bInvertNormal ) +{ + m_bInvertNormal = bInvertNormal; +} + +uno::Any Stripe::getPolyPolygonShape3D() const +{ + drawing::PolyPolygonShape3D aPP; + + aPP.SequenceX.realloc(1); + aPP.SequenceY.realloc(1); + aPP.SequenceZ.realloc(1); + + drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray(); + + pOuterSequenceX->realloc(4); + pOuterSequenceY->realloc(4); + pOuterSequenceZ->realloc(4); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + *pInnerSequenceX++ = m_aPoint1.PositionX; + *pInnerSequenceY++ = m_aPoint1.PositionY; + *pInnerSequenceZ++ = m_aPoint1.PositionZ; + + *pInnerSequenceX++ = m_aPoint2.PositionX; + *pInnerSequenceY++ = m_aPoint2.PositionY; + *pInnerSequenceZ++ = m_aPoint2.PositionZ; + + *pInnerSequenceX++ = m_aPoint3.PositionX; + *pInnerSequenceY++ = m_aPoint3.PositionY; + *pInnerSequenceZ++ = m_aPoint3.PositionZ; + + *pInnerSequenceX++ = m_aPoint4.PositionX; + *pInnerSequenceY++ = m_aPoint4.PositionY; + *pInnerSequenceZ++ = m_aPoint4.PositionZ; + + return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get()); +} + +drawing::Direction3D Stripe::getNormal() const +{ + drawing::Direction3D aRet(1.0,0.0,0.0); + + if( m_bManualNormalSet ) + aRet = m_aManualNormal; + else + { + ::basegfx::B3DPolygon aPolygon3D; + aPolygon3D.append(Position3DToB3DPoint( m_aPoint1 )); + aPolygon3D.append(Position3DToB3DPoint( m_aPoint2 )); + aPolygon3D.append(Position3DToB3DPoint( m_aPoint3 )); + aPolygon3D.append(Position3DToB3DPoint( m_aPoint4 )); + ::basegfx::B3DVector aNormal(aPolygon3D.getNormal()); + aRet = B3DVectorToDirection3D(aNormal); + } + + if( m_bInvertNormal ) + { + aRet.DirectionX *= -1.0; + aRet.DirectionY *= -1.0; + aRet.DirectionZ *= -1.0; + } + return aRet; +} + +uno::Any Stripe::getNormalsPolygon() const +{ + drawing::PolyPolygonShape3D aPP; + + aPP.SequenceX.realloc(1); + aPP.SequenceY.realloc(1); + aPP.SequenceZ.realloc(1); + + drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray(); + + pOuterSequenceX->realloc(4); + pOuterSequenceY->realloc(4); + pOuterSequenceZ->realloc(4); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + drawing::Direction3D aNormal( getNormal() ); + + for(sal_Int32 nN=4; --nN; ) + { + *pInnerSequenceX++ = aNormal.DirectionX; + *pInnerSequenceY++ = aNormal.DirectionY; + *pInnerSequenceZ++ = aNormal.DirectionZ; + } + return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get()); +} + +uno::Any Stripe::getTexturePolygon( short nRotatedTexture ) +{ + drawing::PolyPolygonShape3D aPP; + + aPP.SequenceX.realloc(1); + aPP.SequenceY.realloc(1); + aPP.SequenceZ.realloc(1); + + drawing::DoubleSequence* pOuterSequenceX = aPP.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPP.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPP.SequenceZ.getArray(); + + pOuterSequenceX->realloc(4); + pOuterSequenceY->realloc(4); + pOuterSequenceZ->realloc(4); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + if( nRotatedTexture==0 ) + { + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + } + else if( nRotatedTexture==1 ) + { + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + } + else if( nRotatedTexture==2 ) + { + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + } + else if( nRotatedTexture==3 ) + { + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + } + else if( nRotatedTexture==4 ) + { + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + } + else if( nRotatedTexture==5 ) + { + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + } + else if( nRotatedTexture==6 ) + { + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + } + else if( nRotatedTexture==7 ) + { + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 1.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 0.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + + *pInnerSequenceX++ = 1.0; + *pInnerSequenceY++ = 0.0; + *pInnerSequenceZ++ = 0.0; + } + + return uno::Any( &aPP, cppu::UnoType<drawing::PolyPolygonShape3D>::get()); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VButton.cxx b/chart2/source/view/main/VButton.cxx new file mode 100644 index 000000000..088df5850 --- /dev/null +++ b/chart2/source/view/main/VButton.cxx @@ -0,0 +1,154 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "VButton.hxx" + +#include <ShapeFactory.hxx> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <memory> + +#include <CommonConverters.hxx> +#include <editeng/unoprnms.hxx> + +namespace chart +{ + +using namespace css; + +VButton::VButton() + : m_bShowArrow(true) + , m_nArrowColor(0x00000000) + , m_nBGColor(0x00E6E6E6) +{ +} + +void VButton::init(const uno::Reference<drawing::XShapes>& xTargetPage, + const uno::Reference<lang::XMultiServiceFactory>& xFactory) +{ + m_xTarget = xTargetPage; + m_xShapeFactory = xFactory; +} + +uno::Reference<drawing::XShape> VButton::createTriangle(awt::Size aSize) +{ + uno::Reference<drawing::XShape> xShape; + xShape.set(m_xShapeFactory->createInstance("com.sun.star.drawing.PolyPolygonShape"), uno::UNO_QUERY); + + if (!xShape.is()) + return xShape; + + uno::Reference<beans::XPropertySet> xProperies(xShape, uno::UNO_QUERY); + + drawing::PolyPolygonShape3D aPolyPolygon; + aPolyPolygon.SequenceX.realloc(1); + aPolyPolygon.SequenceY.realloc(1); + aPolyPolygon.SequenceZ.realloc(1); + + drawing::DoubleSequence* pOuterSequenceX = aPolyPolygon.SequenceX.getArray(); + drawing::DoubleSequence* pOuterSequenceY = aPolyPolygon.SequenceY.getArray(); + drawing::DoubleSequence* pOuterSequenceZ = aPolyPolygon.SequenceZ.getArray(); + + pOuterSequenceX->realloc(3); + pOuterSequenceY->realloc(3); + pOuterSequenceZ->realloc(3); + + double* pInnerSequenceX = pOuterSequenceX->getArray(); + double* pInnerSequenceY = pOuterSequenceY->getArray(); + double* pInnerSequenceZ = pOuterSequenceZ->getArray(); + + pInnerSequenceX[0] = 0.0; + pInnerSequenceY[0] = 0.0; + pInnerSequenceZ[0] = 0.0; + + pInnerSequenceX[1] = aSize.Width / 2.0; + pInnerSequenceY[1] = aSize.Height; + pInnerSequenceZ[1] = 0.0; + + pInnerSequenceX[2] = aSize.Width; + pInnerSequenceY[2] = 0.0; + pInnerSequenceZ[2] = 0.0; + + xProperies->setPropertyValue("Name", uno::makeAny(m_sCID)); + xProperies->setPropertyValue(UNO_NAME_POLYPOLYGON, uno::Any(PolyToPointSequence(aPolyPolygon))); + xProperies->setPropertyValue("LineStyle", uno::makeAny(drawing::LineStyle_NONE)); + xProperies->setPropertyValue("FillColor", uno::makeAny(m_nArrowColor)); + + return xShape; +} + +void VButton::createShapes(const uno::Reference<beans::XPropertySet>& xTextProp) +{ + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory); + + std::unique_ptr<tNameSequence> pPropNames(new tNameSequence); + std::unique_ptr<tAnySequence> pPropValues(new tAnySequence); + + PropertyMapper::getTextLabelMultiPropertyLists(xTextProp, *pPropNames, *pPropValues); + + m_xShape.set(pShapeFactory->createGroup2D(m_xTarget, m_sCID), uno::UNO_QUERY); + m_xShape->setPosition(m_aPosition); + m_xShape->setSize(m_aSize); + + uno::Reference<drawing::XShapes> xContainer(m_xShape, uno::UNO_QUERY); + if (!xContainer.is()) + return; + + tPropertyNameValueMap aTextValueMap; + aTextValueMap["CharHeight"] <<= 10.0f; + aTextValueMap["CharHeightAsian"] <<= 10.0f; + aTextValueMap["CharHeightComplex"] <<= 10.0f; + aTextValueMap["FillColor"] <<= m_nBGColor; + aTextValueMap["FillStyle"] <<= drawing::FillStyle_SOLID; + aTextValueMap["LineColor"] <<= sal_Int32(0xcccccc); + aTextValueMap["LineStyle"] <<= drawing::LineStyle_SOLID; + aTextValueMap["ParaAdjust"] <<= style::ParagraphAdjust_CENTER; + aTextValueMap["TextHorizontalAdjust"] <<= drawing::TextHorizontalAdjust_LEFT; + aTextValueMap["TextVerticalAdjust"] <<= drawing::TextVerticalAdjust_CENTER; + aTextValueMap["ParaLeftMargin"] <<= sal_Int32(100); + aTextValueMap["ParaRightMargin"] <<= sal_Int32(600); + + aTextValueMap["Name"] <<= m_sCID; //CID OUString + + PropertyMapper::getMultiPropertyListsFromValueMap(*pPropNames, *pPropValues, aTextValueMap); + + uno::Reference<drawing::XShape> xEntry = pShapeFactory->createText( + xContainer, m_sLabel, *pPropNames, *pPropValues, uno::Any()); + + if (xEntry.is()) + { + xEntry->setPosition(m_aPosition); + xEntry->setSize(m_aSize); + } + + if (!m_bShowArrow) + return; + + awt::Size aPolySize {280, 180}; + + uno::Reference<drawing::XShape> xPoly = createTriangle(aPolySize); + if (xPoly.is()) + { + xPoly->setSize(aPolySize); + xPoly->setPosition({ sal_Int32(m_aPosition.X + m_aSize.Width - aPolySize.Width - 100), + sal_Int32(m_aPosition.Y + (m_aSize.Height / 2.0) - (aPolySize.Height / 2.0)) }); + xContainer->add(xPoly); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VButton.hxx b/chart2/source/view/main/VButton.hxx new file mode 100644 index 000000000..cb4203326 --- /dev/null +++ b/chart2/source/view/main/VButton.hxx @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_CHART2_SOURCE_VIEW_MAIN_VBUTTON_HXX +#define INCLUDED_CHART2_SOURCE_VIEW_MAIN_VBUTTON_HXX + +#include <tools/color.hxx> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/uno/Reference.hxx> + +namespace com::sun::star::beans { class XPropertySet; } +namespace com::sun::star::drawing { class XShape; } +namespace com::sun::star::drawing { class XShapes; } +namespace com::sun::star::lang { class XMultiServiceFactory; } + +namespace chart +{ + +class VButton final +{ +private: + css::uno::Reference<css::lang::XMultiServiceFactory> m_xShapeFactory; + css::uno::Reference<css::drawing::XShapes> m_xTarget; + css::uno::Reference<css::drawing::XShape> m_xShape; + OUString m_sLabel; + OUString m_sCID; + css::awt::Point m_aPosition; + css::awt::Size m_aSize; + bool m_bShowArrow; + Color m_nArrowColor; + Color m_nBGColor; + + css::uno::Reference<css::drawing::XShape> + createTriangle(css::awt::Size aSize); + +public: + VButton(); + + void init(const css::uno::Reference<css::drawing::XShapes>& xTargetPage, + const css::uno::Reference<css::lang::XMultiServiceFactory>& xFactory); + + void createShapes(const css::uno::Reference<css::beans::XPropertySet>& xTextProp); + + void showArrow(bool bShowArrow) + { + m_bShowArrow = bShowArrow; + } + void setArrowColor(Color nArrowColor) + { + m_nArrowColor = nArrowColor; + } + void setBGColor(Color nBGColor) + { + m_nBGColor = nBGColor; + } + void setLabel(OUString const & rLabel) + { + m_sLabel = rLabel; + } + void setCID(OUString const & rCID) + { + m_sCID = rCID; + } + void setPosition(css::awt::Point const & rPosition) + { + m_aPosition = rPosition; + } + css::awt::Size const & getSize() const + { + return m_aSize; + } + void setSize(css::awt::Size const & rSize) + { + m_aSize = rSize; + } +}; + +} //namespace chart +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VDataSeries.cxx b/chart2/source/view/main/VDataSeries.cxx new file mode 100644 index 000000000..91388d1c2 --- /dev/null +++ b/chart2/source/view/main/VDataSeries.cxx @@ -0,0 +1,1150 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <VDataSeries.hxx> +#include <ObjectIdentifier.hxx> +#include <CommonConverters.hxx> +#include <LabelPositionHelper.hxx> +#include <ChartTypeHelper.hxx> +#include <RegressionCurveHelper.hxx> +#include <unonames.hxx> + +#include <com/sun/star/chart/DataLabelPlacement.hpp> +#include <com/sun/star/chart/MissingValueTreatment.hpp> +#include <com/sun/star/chart2/DataPointLabel.hpp> +#include <com/sun/star/chart2/Symbol.hpp> +#include <com/sun/star/chart2/XDataSeries.hpp> +#include <com/sun/star/chart2/XRegressionCurveCalculator.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> + +#include <rtl/math.hxx> +#include <osl/diagnose.h> +#include <tools/color.hxx> +#include <tools/diagnose_ex.h> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/chart2/data/XDataSource.hpp> + +namespace chart { + +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; +using ::com::sun::star::uno::Reference; + +void VDataSequence::init( const uno::Reference< data::XDataSequence >& xModel ) +{ + Model = xModel; + Doubles = DataSequenceToDoubleSequence( xModel ); +} + +bool VDataSequence::is() const +{ + return Model.is(); +} +void VDataSequence::clear() +{ + Model = nullptr; + Doubles.realloc(0); +} + +double VDataSequence::getValue( sal_Int32 index ) const +{ + if( 0<=index && index<Doubles.getLength() ) + return Doubles[index]; + else + { + double fNan; + ::rtl::math::setNan( & fNan ); + return fNan; + } +} + +sal_Int32 VDataSequence::detectNumberFormatKey( sal_Int32 index ) const +{ + sal_Int32 nNumberFormatKey = -1; + + // -1 is allowed and means a key for the whole sequence + if( -1<=index && index<Doubles.getLength() && + Model.is()) + { + nNumberFormatKey = Model->getNumberFormatKeyByIndex( index ); + } + + return nNumberFormatKey; +} + +sal_Int32 VDataSequence::getLength() const +{ + return Doubles.getLength(); +} + +namespace +{ +struct lcl_LessXOfPoint +{ + bool operator() ( const std::vector< double >& first, + const std::vector< double >& second ) + { + if( !first.empty() && !second.empty() ) + { + return first[0]<second[0]; + } + return false; + } +}; + +void lcl_clearIfNoValuesButTextIsContained( VDataSequence& rData, const uno::Reference<data::XDataSequence>& xDataSequence ) +{ + //#i71686#, #i101968#, #i102428# + sal_Int32 nCount = rData.Doubles.getLength(); + for( sal_Int32 i = 0; i < nCount; ++i ) + { + if( !std::isnan( rData.Doubles[i] ) ) + return; + } + //no double value is contained + //is there any text? + uno::Sequence< OUString > aStrings( DataSequenceToStringSequence( xDataSequence ) ); + sal_Int32 nTextCount = aStrings.getLength(); + for( sal_Int32 j = 0; j < nTextCount; ++j ) + { + if( !aStrings[j].isEmpty() ) + { + rData.clear(); + return; + } + } + //no content at all +} + +void lcl_maybeReplaceNanWithZero( double& rfValue, sal_Int32 nMissingValueTreatment ) +{ + if( nMissingValueTreatment == css::chart::MissingValueTreatment::USE_ZERO + && (std::isnan(rfValue) || std::isinf(rfValue)) ) + rfValue = 0.0; +} + +} + +VDataSeries::VDataSeries( const uno::Reference< XDataSeries >& xDataSeries ) + : m_nPolygonIndex(0) + , m_fLogicMinX(0.0) + , m_fLogicMaxX(0.0) + , m_fLogicZPos(0.0) + , m_xDataSeries(xDataSeries) + , m_nPointCount(0) + + , m_pValueSequenceForDataLabelNumberFormatDetection(&m_aValues_Y) + + , m_fXMeanValue(1.0) + , m_fYMeanValue(1.0) + + , m_aAttributedDataPointIndexList() + + , m_eStackingDirection(StackingDirection_NO_STACKING) + , m_nAxisIndex(0) + , m_bConnectBars(false) + , m_bGroupBarsPerAxis(true) + , m_nStartingAngle(90) + + , m_nGlobalSeriesIndex(0) + + , m_nCurrentAttributedPoint(-1) + , m_nMissingValueTreatment(css::chart::MissingValueTreatment::LEAVE_GAP) + , m_bAllowPercentValueInDataLabel(false) + , mpOldSeries(nullptr) + , mnPercent(0.0) +{ + ::rtl::math::setNan( & m_fXMeanValue ); + ::rtl::math::setNan( & m_fYMeanValue ); + + uno::Reference<data::XDataSource> xDataSource( xDataSeries, uno::UNO_QUERY ); + + uno::Sequence< uno::Reference< + chart2::data::XLabeledDataSequence > > aDataSequences = + xDataSource->getDataSequences(); + + for(sal_Int32 nN = aDataSequences.getLength();nN--;) + { + if(!aDataSequences[nN].is()) + continue; + uno::Reference<data::XDataSequence> xDataSequence( aDataSequences[nN]->getValues()); + uno::Reference<beans::XPropertySet> xProp(xDataSequence, uno::UNO_QUERY ); + if( xProp.is()) + { + try + { + uno::Any aARole = xProp->getPropertyValue("Role"); + OUString aRole; + aARole >>= aRole; + + if (aRole == "values-x") + { + m_aValues_X.init( xDataSequence ); + lcl_clearIfNoValuesButTextIsContained( m_aValues_X, xDataSequence ); + } + else if (aRole =="values-y") + m_aValues_Y.init( xDataSequence ); + else if (aRole == "values-min") + m_aValues_Y_Min.init( xDataSequence ); + else if (aRole == "values-max") + m_aValues_Y_Max.init( xDataSequence ); + else if (aRole == "values-first") + m_aValues_Y_First.init( xDataSequence ); + else if (aRole == "values-last") + m_aValues_Y_Last.init( xDataSequence ); + else if (aRole == "values-size") + m_aValues_Bubble_Size.init( xDataSequence ); + else + { + VDataSequence aSequence; + aSequence.init(xDataSequence); + m_PropertyMap.insert(std::make_pair(aRole, aSequence)); + } + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + } + + //determine the point count + m_nPointCount = m_aValues_Y.getLength(); + { + if( m_nPointCount < m_aValues_Bubble_Size.getLength() ) + m_nPointCount = m_aValues_Bubble_Size.getLength(); + if( m_nPointCount < m_aValues_Y_Min.getLength() ) + m_nPointCount = m_aValues_Y_Min.getLength(); + if( m_nPointCount < m_aValues_Y_Max.getLength() ) + m_nPointCount = m_aValues_Y_Max.getLength(); + if( m_nPointCount < m_aValues_Y_First.getLength() ) + m_nPointCount = m_aValues_Y_First.getLength(); + if( m_nPointCount < m_aValues_Y_Last.getLength() ) + m_nPointCount = m_aValues_Y_Last.getLength(); + } + + uno::Reference<beans::XPropertySet> xProp(xDataSeries, uno::UNO_QUERY ); + if( !xProp.is()) + return; + + try + { + //get AttributedDataPoints + xProp->getPropertyValue("AttributedDataPoints") >>= m_aAttributedDataPointIndexList; + + xProp->getPropertyValue("StackingDirection") >>= m_eStackingDirection; + + xProp->getPropertyValue("AttachedAxisIndex") >>= m_nAxisIndex; + if(m_nAxisIndex<0) + m_nAxisIndex=0; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } +} + +VDataSeries::~VDataSeries() +{ +} + +void VDataSeries::doSortByXValues() +{ + if( !(m_aValues_X.is() && m_aValues_X.Doubles.hasElements()) ) + return; + + //prepare a vector for sorting + std::vector< std::vector< double > > aTmp;//outer vector are points, inner vector are the different values of the point + double fNan; + ::rtl::math::setNan( & fNan ); + sal_Int32 nPointIndex = 0; + for( nPointIndex=0; nPointIndex < m_nPointCount; nPointIndex++ ) + { + std::vector< double > aSinglePoint; + aSinglePoint.push_back( (nPointIndex < m_aValues_X.Doubles.getLength()) ? m_aValues_X.Doubles[nPointIndex] : fNan ); + aSinglePoint.push_back( (nPointIndex < m_aValues_Y.Doubles.getLength()) ? m_aValues_Y.Doubles[nPointIndex] : fNan ); + aTmp.push_back( aSinglePoint ); + } + + //do sort + std::stable_sort( aTmp.begin(), aTmp.end(), lcl_LessXOfPoint() ); + + //fill the sorted points back to the members + m_aValues_X.Doubles.realloc( m_nPointCount ); + m_aValues_Y.Doubles.realloc( m_nPointCount ); + + for( nPointIndex=0; nPointIndex < m_nPointCount; nPointIndex++ ) + { + m_aValues_X.Doubles[nPointIndex]=aTmp[nPointIndex][0]; + m_aValues_Y.Doubles[nPointIndex]=aTmp[nPointIndex][1]; + } +} + +void VDataSeries::releaseShapes() +{ + m_xGroupShape.set(nullptr); + m_xLabelsGroupShape.set(nullptr); + m_xErrorXBarsGroupShape.set(nullptr); + m_xErrorYBarsGroupShape.set(nullptr); + m_xFrontSubGroupShape.set(nullptr); + m_xBackSubGroupShape.set(nullptr); + + m_aPolyPolygonShape3D.SequenceX.realloc(0); + m_aPolyPolygonShape3D.SequenceY.realloc(0); + m_aPolyPolygonShape3D.SequenceZ.realloc(0); + m_nPolygonIndex = 0; +} + +const uno::Reference<css::chart2::XDataSeries>& VDataSeries::getModel() const +{ + return m_xDataSeries; +} + +void VDataSeries::setCategoryXAxis() +{ + m_aValues_X.clear(); + m_bAllowPercentValueInDataLabel = true; +} + +void VDataSeries::setXValues( const Reference< chart2::data::XDataSequence >& xValues ) +{ + m_aValues_X.clear(); + m_aValues_X.init( xValues ); + m_bAllowPercentValueInDataLabel = true; +} + +void VDataSeries::setXValuesIfNone( const Reference< chart2::data::XDataSequence >& xValues ) +{ + if( m_aValues_X.is() ) + return; + + m_aValues_X.init( xValues ); + lcl_clearIfNoValuesButTextIsContained( m_aValues_X, xValues ); +} + +void VDataSeries::setGlobalSeriesIndex( sal_Int32 nGlobalSeriesIndex ) +{ + m_nGlobalSeriesIndex = nGlobalSeriesIndex; +} + +void VDataSeries::setParticle( const OUString& rSeriesParticle ) +{ + m_aSeriesParticle = rSeriesParticle; + + //get CID + m_aCID = ObjectIdentifier::createClassifiedIdentifierForParticle( m_aSeriesParticle ); + m_aPointCID_Stub = ObjectIdentifier::createSeriesSubObjectStub( OBJECTTYPE_DATA_POINT, m_aSeriesParticle ); + + m_aLabelCID_Stub = ObjectIdentifier::createClassifiedIdentifierWithParent( + OBJECTTYPE_DATA_LABEL, OUString(), getLabelsCID() ); +} +OUString VDataSeries::getErrorBarsCID(bool bYError) const +{ + OUString aChildParticle( ObjectIdentifier::getStringForType( + bYError ? OBJECTTYPE_DATA_ERRORS_Y : OBJECTTYPE_DATA_ERRORS_X ) + + "=" ); + + return ObjectIdentifier::createClassifiedIdentifierForParticles( + m_aSeriesParticle, aChildParticle ); +} +OUString VDataSeries::getLabelsCID() const +{ + OUString aChildParticle( ObjectIdentifier::getStringForType( OBJECTTYPE_DATA_LABELS ) + "=" ); + + return ObjectIdentifier::createClassifiedIdentifierForParticles( + m_aSeriesParticle, aChildParticle ); +} +OUString VDataSeries::getDataCurveCID( sal_Int32 nCurveIndex, bool bAverageLine ) const +{ + return ObjectIdentifier::createDataCurveCID( m_aSeriesParticle, nCurveIndex, bAverageLine ); +} + +OUString VDataSeries::getDataCurveEquationCID( sal_Int32 nCurveIndex ) const +{ + return ObjectIdentifier::createDataCurveEquationCID( m_aSeriesParticle, nCurveIndex ); +} +void VDataSeries::setPageReferenceSize( const awt::Size & rPageRefSize ) +{ + m_aReferenceSize = rPageRefSize; +} + +void VDataSeries::setConnectBars( bool bConnectBars ) +{ + m_bConnectBars = bConnectBars; +} + +bool VDataSeries::getConnectBars() const +{ + return m_bConnectBars; +} + +void VDataSeries::setGroupBarsPerAxis( bool bGroupBarsPerAxis ) +{ + m_bGroupBarsPerAxis = bGroupBarsPerAxis; +} + +bool VDataSeries::getGroupBarsPerAxis() const +{ + return m_bGroupBarsPerAxis; +} + +void VDataSeries::setStartingAngle( sal_Int32 nStartingAngle ) +{ + m_nStartingAngle = nStartingAngle; +} + +sal_Int32 VDataSeries::getStartingAngle() const +{ + return m_nStartingAngle; +} + +chart2::StackingDirection VDataSeries::getStackingDirection() const +{ + return m_eStackingDirection; +} + +sal_Int32 VDataSeries::getAttachedAxisIndex() const +{ + return m_nAxisIndex; +} + +void VDataSeries::setAttachedAxisIndex( sal_Int32 nAttachedAxisIndex ) +{ + if( nAttachedAxisIndex < 0 ) + nAttachedAxisIndex = 0; + m_nAxisIndex = nAttachedAxisIndex; +} + +double VDataSeries::getXValue( sal_Int32 index ) const +{ + double fRet = 0.0; + if(m_aValues_X.is()) + { + if( 0<=index && index<m_aValues_X.getLength() ) + { + fRet = m_aValues_X.Doubles[index]; + if(mpOldSeries && index < mpOldSeries->m_aValues_X.getLength()) + { + double nOldVal = mpOldSeries->m_aValues_X.Doubles[index]; + fRet = nOldVal + (fRet - nOldVal) * mnPercent; + } + } + else + ::rtl::math::setNan( &fRet ); + } + else + { + // #i70133# always return correct X position - needed for short data series + if( 0<=index /*&& index < m_nPointCount*/ ) + fRet = index+1;//first category (index 0) matches with real number 1.0 + else + ::rtl::math::setNan( &fRet ); + } + lcl_maybeReplaceNanWithZero( fRet, getMissingValueTreatment() ); + return fRet; +} + +double VDataSeries::getYValue( sal_Int32 index ) const +{ + double fRet = 0.0; + if(m_aValues_Y.is()) + { + if( 0<=index && index<m_aValues_Y.getLength() ) + { + fRet = m_aValues_Y.Doubles[index]; + if(mpOldSeries && index < mpOldSeries->m_aValues_Y.getLength()) + { + double nOldVal = mpOldSeries->m_aValues_Y.Doubles[index]; + fRet = nOldVal + (fRet - nOldVal) * mnPercent; + } + } + else + ::rtl::math::setNan( &fRet ); + } + else + { + // #i70133# always return correct X position - needed for short data series + if( 0<=index /*&& index < m_nPointCount*/ ) + fRet = index+1;//first category (index 0) matches with real number 1.0 + else + ::rtl::math::setNan( &fRet ); + } + lcl_maybeReplaceNanWithZero( fRet, getMissingValueTreatment() ); + return fRet; +} + +void VDataSeries::getMinMaxXValue(double& fMin, double& fMax) const +{ + rtl::math::setNan( &fMax ); + rtl::math::setNan( &fMin ); + + uno::Sequence< double > aValuesX = getAllX(); + + if(!aValuesX.hasElements()) + return; + + sal_Int32 i = 0; + while ( i < aValuesX.getLength() && std::isnan(aValuesX[i]) ) + i++; + if ( i < aValuesX.getLength() ) + fMax = fMin = aValuesX[i++]; + + for ( ; i < aValuesX.getLength(); i++) + { + const double aValue = aValuesX[i]; + if ( aValue > fMax) + { + fMax = aValue; + } + else if ( aValue < fMin) + { + fMin = aValue; + } + } +} +double VDataSeries::getY_Min( sal_Int32 index ) const +{ + return m_aValues_Y_Min.getValue( index ); +} +double VDataSeries::getY_Max( sal_Int32 index ) const +{ + return m_aValues_Y_Max.getValue( index ); +} +double VDataSeries::getY_First( sal_Int32 index ) const +{ + return m_aValues_Y_First.getValue( index ); +} +double VDataSeries::getY_Last( sal_Int32 index ) const +{ + return m_aValues_Y_Last.getValue( index ); +} +double VDataSeries::getBubble_Size( sal_Int32 index ) const +{ + double nNewVal = m_aValues_Bubble_Size.getValue( index ); + if(mpOldSeries && index < mpOldSeries->m_aValues_Bubble_Size.getLength()) + { + double nOldVal = mpOldSeries->m_aValues_Bubble_Size.getValue( index ); + nNewVal = nOldVal + (nNewVal - nOldVal) * mnPercent; + } + + return nNewVal; +} + +bool VDataSeries::hasExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const +{ + OUString aPropName = bForPercentage ? OUString("PercentageNumberFormat") : OUString(CHART_UNONAME_NUMFMT); + bool bHasNumberFormat = false; + bool bLinkToSource = true; + uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( nPointIndex )); + sal_Int32 nNumberFormat = -1; + if( xPointProp.is() && (xPointProp->getPropertyValue(CHART_UNONAME_LINK_TO_SRC_NUMFMT) >>= bLinkToSource)) + { + if( !bLinkToSource && (xPointProp->getPropertyValue(aPropName) >>= nNumberFormat)) + bHasNumberFormat = true; + } + return bHasNumberFormat; +} +sal_Int32 VDataSeries::getExplicitNumberFormat( sal_Int32 nPointIndex, bool bForPercentage ) const +{ + OUString aPropName = bForPercentage ? OUString("PercentageNumberFormat") : OUString(CHART_UNONAME_NUMFMT); + sal_Int32 nNumberFormat = -1; + uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( nPointIndex )); + if( xPointProp.is() ) + xPointProp->getPropertyValue(aPropName) >>= nNumberFormat; + return nNumberFormat; +} +void VDataSeries::setRoleOfSequenceForDataLabelNumberFormatDetection( const OUString& rRole ) +{ + if (rRole == "values-y") + m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y; + else if (rRole == "values-size") + m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Bubble_Size; + else if (rRole == "values-min") + m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Min; + else if (rRole == "values-max") + m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Max; + else if (rRole == "values-first") + m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_First; + else if (rRole == "values-last") + m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_Y_Last; + else if (rRole == "values-x") + m_pValueSequenceForDataLabelNumberFormatDetection = &m_aValues_X; +} +sal_Int32 VDataSeries::detectNumberFormatKey( sal_Int32 index ) const +{ + sal_Int32 nRet = 0; + if( m_pValueSequenceForDataLabelNumberFormatDetection ) + nRet = m_pValueSequenceForDataLabelNumberFormatDetection->detectNumberFormatKey( index ); + return nRet; +} + +sal_Int32 VDataSeries::getLabelPlacement( sal_Int32 nPointIndex, const uno::Reference< chart2::XChartType >& xChartType, bool bSwapXAndY ) const +{ + sal_Int32 nLabelPlacement=0; + try + { + uno::Reference< beans::XPropertySet > xPointProps( getPropertiesOfPoint( nPointIndex ) ); + if( xPointProps.is() ) + xPointProps->getPropertyValue("LabelPlacement") >>= nLabelPlacement; + + uno::Sequence < sal_Int32 > aAvailablePlacements( ChartTypeHelper::getSupportedLabelPlacements( + xChartType, bSwapXAndY, m_xDataSeries ) ); + + for( sal_Int32 n : aAvailablePlacements ) + if( n == nLabelPlacement ) + return nLabelPlacement; //ok + + //otherwise use the first supported one + if( aAvailablePlacements.hasElements() ) + { + nLabelPlacement = aAvailablePlacements[0]; + if( xPointProps.is() ) + xPointProps->setPropertyValue("LabelPlacement", uno::Any(nLabelPlacement)); + return nLabelPlacement; + } + + OSL_FAIL("no label placement supported"); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return nLabelPlacement; +} + +awt::Point VDataSeries::getLabelPosition( awt::Point aTextShapePos, sal_Int32 nPointIndex ) const +{ + awt::Point aPos(-1, -1); + try + { + RelativePosition aCustomLabelPosition; + uno::Reference< beans::XPropertySet > xPointProps(getPropertiesOfPoint(nPointIndex)); + if( xPointProps.is() && (xPointProps->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition)) + { + aPos.X = static_cast<sal_Int32>(aCustomLabelPosition.Primary * m_aReferenceSize.Width) + aTextShapePos.X; + aPos.Y = static_cast<sal_Int32>(aCustomLabelPosition.Secondary * m_aReferenceSize.Height) + aTextShapePos.Y; + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("chart2", ""); + } + return aPos; +} + +bool VDataSeries::isLabelCustomPos(sal_Int32 nPointIndex) const +{ + bool bCustom = false; + RelativePosition aCustomLabelPosition; + try + { + if( isAttributedDataPoint(nPointIndex) ) + { + uno::Reference< beans::XPropertySet > xPointProps(m_xDataSeries->getDataPointByIndex(nPointIndex)); + if( xPointProps.is() && (xPointProps->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition) ) + bCustom = true; + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("chart2", ""); + } + return bCustom; +} + +double VDataSeries::getMinimumofAllDifferentYValues( sal_Int32 index ) const +{ + double fMin=0.0; + ::rtl::math::setInf(&fMin, false); + + if( !m_aValues_Y.is() && + (m_aValues_Y_Min.is() || m_aValues_Y_Max.is() + || m_aValues_Y_First.is() || m_aValues_Y_Last.is() ) ) + { + double fY_Min = getY_Min( index ); + double fY_Max = getY_Max( index ); + double fY_First = getY_First( index ); + double fY_Last = getY_Last( index ); + + if(fMin>fY_First) + fMin=fY_First; + if(fMin>fY_Last) + fMin=fY_Last; + if(fMin>fY_Min) + fMin=fY_Min; + if(fMin>fY_Max) + fMin=fY_Max; + } + else + { + double fY = getYValue( index ); + if(fMin>fY) + fMin=fY; + } + + if( std::isinf(fMin) ) + ::rtl::math::setNan(&fMin); + + return fMin; +} + +double VDataSeries::getMaximumofAllDifferentYValues( sal_Int32 index ) const +{ + double fMax=0.0; + ::rtl::math::setInf(&fMax, true); + + if( !m_aValues_Y.is() && + (m_aValues_Y_Min.is() || m_aValues_Y_Max.is() + || m_aValues_Y_First.is() || m_aValues_Y_Last.is() ) ) + { + double fY_Min = getY_Min( index ); + double fY_Max = getY_Max( index ); + double fY_First = getY_First( index ); + double fY_Last = getY_Last( index ); + + if(fMax<fY_First) + fMax=fY_First; + if(fMax<fY_Last) + fMax=fY_Last; + if(fMax<fY_Min) + fMax=fY_Min; + if(fMax<fY_Max) + fMax=fY_Max; + } + else + { + double fY = getYValue( index ); + if(fMax<fY) + fMax=fY; + } + + if( std::isinf(fMax) ) + ::rtl::math::setNan(&fMax); + + return fMax; +} + +uno::Sequence< double > const & VDataSeries::getAllX() const +{ + if(!m_aValues_X.is() && !m_aValues_X.getLength() && m_nPointCount) + { + //init x values from category indexes + //first category (index 0) matches with real number 1.0 + m_aValues_X.Doubles.realloc( m_nPointCount ); + for(sal_Int32 nN=m_aValues_X.getLength();nN--;) + m_aValues_X.Doubles[nN] = nN+1; + } + return m_aValues_X.Doubles; +} + +uno::Sequence< double > const & VDataSeries::getAllY() const +{ + if(!m_aValues_Y.is() && !m_aValues_Y.getLength() && m_nPointCount) + { + //init y values from indexes + //first y-value (index 0) matches with real number 1.0 + m_aValues_Y.Doubles.realloc( m_nPointCount ); + for(sal_Int32 nN=m_aValues_Y.getLength();nN--;) + m_aValues_Y.Doubles[nN] = nN+1; + } + return m_aValues_Y.Doubles; +} + +double VDataSeries::getXMeanValue() const +{ + if( std::isnan( m_fXMeanValue ) ) + { + uno::Reference< XRegressionCurveCalculator > xCalculator( RegressionCurveHelper::createRegressionCurveCalculatorByServiceName( "com.sun.star.chart2.MeanValueRegressionCurve" ) ); + uno::Sequence< double > aXValuesDummy; + xCalculator->recalculateRegression( aXValuesDummy, getAllX() ); + m_fXMeanValue = xCalculator->getCurveValue( 1.0 ); + } + return m_fXMeanValue; +} + +double VDataSeries::getYMeanValue() const +{ + if( std::isnan( m_fYMeanValue ) ) + { + uno::Reference< XRegressionCurveCalculator > xCalculator( + RegressionCurveHelper::createRegressionCurveCalculatorByServiceName("com.sun.star.chart2.MeanValueRegressionCurve")); + uno::Sequence< double > aXValuesDummy; + xCalculator->recalculateRegression( aXValuesDummy, getAllY() ); + m_fYMeanValue = xCalculator->getCurveValue( 1.0 ); + } + return m_fYMeanValue; +} + +static std::unique_ptr<Symbol> getSymbolPropertiesFromPropertySet( const uno::Reference< beans::XPropertySet >& xProp ) +{ + std::unique_ptr< Symbol > apSymbolProps( new Symbol() ); + try + { + if( xProp->getPropertyValue("Symbol") >>= *apSymbolProps ) + { + //use main color to fill symbols + xProp->getPropertyValue("Color") >>= apSymbolProps->FillColor; + // border of symbols always same as fill color + apSymbolProps->BorderColor = apSymbolProps->FillColor; + } + else + apSymbolProps.reset(); + } + catch(const uno::Exception &) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return apSymbolProps; +} + +Symbol* VDataSeries::getSymbolProperties( sal_Int32 index ) const +{ + Symbol* pRet=nullptr; + if( isAttributedDataPoint( index ) ) + { + adaptPointCache( index ); + if (!m_apSymbolProperties_AttributedPoint) + m_apSymbolProperties_AttributedPoint + = getSymbolPropertiesFromPropertySet(getPropertiesOfPoint(index)); + pRet = m_apSymbolProperties_AttributedPoint.get(); + //if a single data point does not have symbols but the dataseries itself has symbols + //we create an invisible symbol shape to enable selection of that point + if( !pRet || pRet->Style == SymbolStyle_NONE ) + { + if (!m_apSymbolProperties_Series) + m_apSymbolProperties_Series + = getSymbolPropertiesFromPropertySet(getPropertiesOfSeries()); + if( m_apSymbolProperties_Series && m_apSymbolProperties_Series->Style != SymbolStyle_NONE ) + { + if (!m_apSymbolProperties_InvisibleSymbolForSelection) + { + m_apSymbolProperties_InvisibleSymbolForSelection.reset(new Symbol); + m_apSymbolProperties_InvisibleSymbolForSelection->Style = SymbolStyle_STANDARD; + m_apSymbolProperties_InvisibleSymbolForSelection->StandardSymbol = 0;//square + m_apSymbolProperties_InvisibleSymbolForSelection->Size = com::sun::star::awt::Size(0, 0);//tdf#126033 + m_apSymbolProperties_InvisibleSymbolForSelection->BorderColor = 0xff000000;//invisible + m_apSymbolProperties_InvisibleSymbolForSelection->FillColor = 0xff000000;//invisible + } + pRet = m_apSymbolProperties_InvisibleSymbolForSelection.get(); + } + } + } + else + { + if (!m_apSymbolProperties_Series) + m_apSymbolProperties_Series + = getSymbolPropertiesFromPropertySet(getPropertiesOfSeries()); + pRet = m_apSymbolProperties_Series.get(); + } + + if( pRet && pRet->Style == SymbolStyle_AUTO ) + { + pRet->Style = SymbolStyle_STANDARD; + + sal_Int32 nIndex = m_nGlobalSeriesIndex; + if(m_aValues_X.is()) + nIndex++; + pRet->StandardSymbol = nIndex; + } + + return pRet; +} + +uno::Reference< beans::XPropertySet > VDataSeries::getXErrorBarProperties( sal_Int32 index ) const +{ + uno::Reference< beans::XPropertySet > xErrorBarProp; + + uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( index )); + if( xPointProp.is() ) + xPointProp->getPropertyValue(CHART_UNONAME_ERRORBAR_X) >>= xErrorBarProp; + return xErrorBarProp; +} + +uno::Reference< beans::XPropertySet > VDataSeries::getYErrorBarProperties( sal_Int32 index ) const +{ + uno::Reference< beans::XPropertySet > xErrorBarProp; + + uno::Reference< beans::XPropertySet > xPointProp( getPropertiesOfPoint( index )); + if( xPointProp.is() ) + xPointProp->getPropertyValue(CHART_UNONAME_ERRORBAR_Y) >>= xErrorBarProp; + return xErrorBarProp; +} + +bool VDataSeries::hasPointOwnColor( sal_Int32 index ) const +{ + if( !isAttributedDataPoint(index) ) + return false; + + try + { + uno::Reference< beans::XPropertyState > xPointState( getPropertiesOfPoint(index), uno::UNO_QUERY_THROW ); + return (xPointState->getPropertyState("Color") != beans::PropertyState_DEFAULT_VALUE ); + } + catch(const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return false; +} + +bool VDataSeries::isAttributedDataPoint( sal_Int32 index ) const +{ + //returns true if the data point assigned by the given index has set its own properties + if( index>=m_nPointCount || m_nPointCount==0) + return false; + for(sal_Int32 n : m_aAttributedDataPointIndexList) + { + if(index == n) + return true; + } + return false; +} + +bool VDataSeries::isVaryColorsByPoint() const +{ + bool bVaryColorsByPoint = false; + Reference< beans::XPropertySet > xSeriesProp( getPropertiesOfSeries() ); + if( xSeriesProp.is() ) + xSeriesProp->getPropertyValue("VaryColorsByPoint") >>= bVaryColorsByPoint; + return bVaryColorsByPoint; +} + +uno::Reference< beans::XPropertySet > VDataSeries::getPropertiesOfPoint( sal_Int32 index ) const +{ + if( isAttributedDataPoint( index ) ) + return m_xDataSeries->getDataPointByIndex(index); + return getPropertiesOfSeries(); +} + +uno::Reference<beans::XPropertySet> VDataSeries::getPropertiesOfSeries() const +{ + return uno::Reference<css::beans::XPropertySet>(m_xDataSeries, css::uno::UNO_QUERY); +} + +static std::unique_ptr<DataPointLabel> getDataPointLabelFromPropertySet( const uno::Reference< beans::XPropertySet >& xProp ) +{ + std::unique_ptr< DataPointLabel > apLabel( new DataPointLabel() ); + try + { + if( !(xProp->getPropertyValue(CHART_UNONAME_LABEL) >>= *apLabel) ) + apLabel.reset(); + } + catch(const uno::Exception &) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + return apLabel; +} + +void VDataSeries::adaptPointCache( sal_Int32 nNewPointIndex ) const +{ + if( m_nCurrentAttributedPoint != nNewPointIndex ) + { + m_apLabel_AttributedPoint.reset(); + m_apLabelPropNames_AttributedPoint.reset(); + m_apLabelPropValues_AttributedPoint.reset(); + m_apSymbolProperties_AttributedPoint.reset(); + m_nCurrentAttributedPoint = nNewPointIndex; + } +} + +DataPointLabel* VDataSeries::getDataPointLabel( sal_Int32 index ) const +{ + DataPointLabel* pRet = nullptr; + if( isAttributedDataPoint( index ) ) + { + adaptPointCache( index ); + if (!m_apLabel_AttributedPoint) + m_apLabel_AttributedPoint + = getDataPointLabelFromPropertySet(getPropertiesOfPoint(index)); + pRet = m_apLabel_AttributedPoint.get(); + } + else + { + if (!m_apLabel_Series) + m_apLabel_Series + = getDataPointLabelFromPropertySet(getPropertiesOfPoint(index)); + pRet = m_apLabel_Series.get(); + } + if( !m_bAllowPercentValueInDataLabel ) + { + if( pRet ) + pRet->ShowNumberInPercent = false; + } + return pRet; +} + +DataPointLabel* VDataSeries::getDataPointLabelIfLabel( sal_Int32 index ) const +{ + DataPointLabel* pLabel = getDataPointLabel( index ); + if( !pLabel || (!pLabel->ShowNumber && !pLabel->ShowNumberInPercent + && !pLabel->ShowCategoryName ) ) + return nullptr; + return pLabel; +} + +bool VDataSeries::getTextLabelMultiPropertyLists( sal_Int32 index + , tNameSequence*& pPropNames + , tAnySequence*& pPropValues ) const +{ + pPropNames = nullptr; pPropValues = nullptr; + uno::Reference< beans::XPropertySet > xTextProp; + bool bDoDynamicFontResize = false; + if( isAttributedDataPoint( index ) ) + { + adaptPointCache( index ); + if (!m_apLabelPropValues_AttributedPoint) + { + // Cache these properties for this point. + m_apLabelPropNames_AttributedPoint.reset(new tNameSequence); + m_apLabelPropValues_AttributedPoint.reset(new tAnySequence); + xTextProp.set( getPropertiesOfPoint( index )); + PropertyMapper::getTextLabelMultiPropertyLists( + xTextProp, *m_apLabelPropNames_AttributedPoint, *m_apLabelPropValues_AttributedPoint); + bDoDynamicFontResize = true; + } + pPropNames = m_apLabelPropNames_AttributedPoint.get(); + pPropValues = m_apLabelPropValues_AttributedPoint.get(); + } + else + { + if (!m_apLabelPropValues_Series) + { + // Cache these properties for the whole series. + m_apLabelPropNames_Series.reset(new tNameSequence); + m_apLabelPropValues_Series.reset(new tAnySequence); + xTextProp.set( getPropertiesOfPoint( index )); + PropertyMapper::getTextLabelMultiPropertyLists( + xTextProp, *m_apLabelPropNames_Series, *m_apLabelPropValues_Series); + bDoDynamicFontResize = true; + } + pPropNames = m_apLabelPropNames_Series.get(); + pPropValues = m_apLabelPropValues_Series.get(); + } + + if( bDoDynamicFontResize && + pPropNames && pPropValues && + xTextProp.is()) + { + LabelPositionHelper::doDynamicFontResize( *pPropValues, *pPropNames, xTextProp, m_aReferenceSize ); + } + + return (pPropNames && pPropValues); +} + +void VDataSeries::setMissingValueTreatment( sal_Int32 nMissingValueTreatment ) +{ + m_nMissingValueTreatment = nMissingValueTreatment; +} + +sal_Int32 VDataSeries::getMissingValueTreatment() const +{ + return m_nMissingValueTreatment; +} + +VDataSeries::VDataSeries() + : m_nPolygonIndex(0) + , m_fLogicMinX(0) + , m_fLogicMaxX(0) + , m_fLogicZPos(0) + , m_nPointCount(0) + , m_pValueSequenceForDataLabelNumberFormatDetection(nullptr) + , m_fXMeanValue(0) + , m_fYMeanValue(0) + , m_eStackingDirection(chart2::StackingDirection_NO_STACKING) + , m_nAxisIndex(0) + , m_bConnectBars(false) + , m_bGroupBarsPerAxis(false) + , m_nStartingAngle(0) + , m_nGlobalSeriesIndex(0) + , m_nCurrentAttributedPoint(0) + , m_nMissingValueTreatment(0) + , m_bAllowPercentValueInDataLabel(false) + , mpOldSeries(nullptr) + , mnPercent(0) +{ +} + +void VDataSeries::setOldTimeBased( VDataSeries* pOldSeries, double nPercent ) +{ + mnPercent = nPercent; + mpOldSeries = pOldSeries; + mpOldSeries->mpOldSeries = nullptr; +} + +VDataSeries* VDataSeries::createCopyForTimeBased() const +{ + VDataSeries* pNew = new VDataSeries(); + pNew->m_aValues_X = m_aValues_X; + pNew->m_aValues_Y = m_aValues_Y; + pNew->m_aValues_Z = m_aValues_Z; + pNew->m_aValues_Y_Min = m_aValues_Y_Min; + pNew->m_aValues_Y_Max = m_aValues_Y_Max; + pNew->m_aValues_Y_First = m_aValues_Y_First; + pNew->m_aValues_Y_Last = m_aValues_Y_Last; + pNew->m_aValues_Bubble_Size = m_aValues_Bubble_Size; + pNew->m_PropertyMap = m_PropertyMap; + + pNew->m_nPointCount = m_nPointCount; + + return pNew; +} + +double VDataSeries::getValueByProperty( sal_Int32 nIndex, const OUString& rPropName ) const +{ + auto const itr = m_PropertyMap.find(rPropName); + if (itr == m_PropertyMap.end()) + { + double fNan; + ::rtl::math::setNan( &fNan ); + return fNan; + } + + const VDataSequence* pData = &itr->second; + double fValue = pData->getValue(nIndex); + if(mpOldSeries && mpOldSeries->hasPropertyMapping(rPropName)) + { + double fOldValue = mpOldSeries->getValueByProperty( nIndex, rPropName ); + if(rPropName.endsWith("Color")) + { + //optimized interpolation for color values + Color aColor(static_cast<sal_uInt32>(fValue)); + Color aOldColor(static_cast<sal_uInt32>(fOldValue)); + sal_uInt8 r = aOldColor.GetRed() + (aColor.GetRed() - aOldColor.GetRed()) * mnPercent; + sal_uInt8 g = aOldColor.GetGreen() + (aColor.GetGreen() - aOldColor.GetGreen()) * mnPercent; + sal_uInt8 b = aOldColor.GetBlue() + (aColor.GetBlue() - aOldColor.GetBlue()) * mnPercent; + sal_uInt8 t = aOldColor.GetTransparency() + (aColor.GetTransparency() - aOldColor.GetTransparency()) * mnPercent; + Color aRet(t, r, g, b); + return sal_uInt32(aRet); + } + return fOldValue + (fValue - fOldValue) * mnPercent; + } + return fValue; +} + +bool VDataSeries::hasPropertyMapping(const OUString& rPropName ) const +{ + return m_PropertyMap.find(rPropName) != m_PropertyMap.end(); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VLegend.cxx b/chart2/source/view/main/VLegend.cxx new file mode 100644 index 000000000..d16495e42 --- /dev/null +++ b/chart2/source/view/main/VLegend.cxx @@ -0,0 +1,1122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "VLegend.hxx" +#include "VButton.hxx" +#include <PropertyMapper.hxx> +#include <ChartModel.hxx> +#include <ObjectIdentifier.hxx> +#include <RelativePositionHelper.hxx> +#include <ShapeFactory.hxx> +#include <RelativeSizeHelper.hxx> +#include <LegendEntryProvider.hxx> +#include <chartview/DrawModelWrapper.hxx> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/LineJoint.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/chart/ChartLegendExpansion.hpp> +#include <com/sun/star/chart2/LegendPosition.hpp> +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/XFormattedString2.hpp> +#include <com/sun/star/chart2/data/XPivotTableDataProvider.hpp> +#include <com/sun/star/chart2/data/PivotTableFieldEntry.hpp> +#include <rtl/math.hxx> +#include <svl/languageoptions.hxx> +#include <tools/diagnose_ex.h> + +#include <vector> +#include <algorithm> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; + +namespace chart +{ + +namespace +{ + +typedef std::pair< ::chart::tNameSequence, ::chart::tAnySequence > tPropertyValues; + +double lcl_CalcViewFontSize( + const Reference< beans::XPropertySet > & xProp, + const awt::Size & rReferenceSize ) +{ + double fResult = 10.0; + + awt::Size aPropRefSize; + float fFontHeight( 0.0 ); + if( xProp.is() && ( xProp->getPropertyValue( "CharHeight") >>= fFontHeight )) + { + fResult = fFontHeight; + try + { + if( (xProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) && + (aPropRefSize.Height > 0)) + { + fResult = ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize ); + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + } + + // pt -> 1/100th mm + return (fResult * (2540.0 / 72.0)); +} + +void lcl_getProperties( + const Reference< beans::XPropertySet > & xLegendProp, + tPropertyValues & rOutLineFillProperties, + tPropertyValues & rOutTextProperties, + const awt::Size & rReferenceSize ) +{ + // Get Line- and FillProperties from model legend + if( !xLegendProp.is()) + return; + + // set rOutLineFillProperties + ::chart::tPropertyNameValueMap aLineFillValueMap; + ::chart::PropertyMapper::getValueMap( aLineFillValueMap, ::chart::PropertyMapper::getPropertyNameMapForFillAndLineProperties(), xLegendProp ); + + aLineFillValueMap[ "LineJoint" ] <<= drawing::LineJoint_ROUND; + + ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( + rOutLineFillProperties.first, rOutLineFillProperties.second, aLineFillValueMap ); + + // set rOutTextProperties + ::chart::tPropertyNameValueMap aTextValueMap; + ::chart::PropertyMapper::getValueMap( aTextValueMap, ::chart::PropertyMapper::getPropertyNameMapForCharacterProperties(), xLegendProp ); + + aTextValueMap[ "TextAutoGrowHeight" ] <<= true; + aTextValueMap[ "TextAutoGrowWidth" ] <<= true; + aTextValueMap[ "TextHorizontalAdjust" ] <<= drawing::TextHorizontalAdjust_LEFT; + aTextValueMap[ "TextMaximumFrameWidth" ] <<= rReferenceSize.Width; //needs to be overwritten by actual available space in the legend + + // recalculate font size + awt::Size aPropRefSize; + float fFontHeight( 0.0 ); + if( (xLegendProp->getPropertyValue( "ReferencePageSize") >>= aPropRefSize) && + (aPropRefSize.Height > 0) && + (aTextValueMap[ "CharHeight" ] >>= fFontHeight) ) + { + aTextValueMap[ "CharHeight" ] <<= + static_cast< float >( + ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )); + + if( aTextValueMap[ "CharHeightAsian" ] >>= fFontHeight ) + { + aTextValueMap[ "CharHeightAsian" ] <<= + static_cast< float >( + ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )); + } + if( aTextValueMap[ "CharHeightComplex" ] >>= fFontHeight ) + { + aTextValueMap[ "CharHeightComplex" ] <<= + static_cast< float >( + ::chart::RelativeSizeHelper::calculate( fFontHeight, aPropRefSize, rReferenceSize )); + } + } + + ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( + rOutTextProperties.first, rOutTextProperties.second, aTextValueMap ); +} + +awt::Size lcl_createTextShapes( + const std::vector<ViewLegendEntry> & rEntries, + const Reference< lang::XMultiServiceFactory > & xShapeFactory, + const Reference< drawing::XShapes > & xTarget, + std::vector< Reference< drawing::XShape > > & rOutTextShapes, + const tPropertyValues & rTextProperties ) +{ + awt::Size aResult; + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + + for (ViewLegendEntry const & rEntry : rEntries) + { + try + { + OUString aLabelString; + Sequence< Reference< XFormattedString2 > > aLabelSeq = rEntry.aLabel; + for( sal_Int32 i = 0; i < aLabelSeq.getLength(); ++i ) + { + // todo: support more than one text range + if( i == 1 ) + break; + + aLabelString += aLabelSeq[i]->getString(); + // workaround for Issue #i67540# + if( aLabelString.isEmpty()) + aLabelString = " "; + } + + Reference< drawing::XShape > xEntry = + pShapeFactory->createText( xTarget, aLabelString, + rTextProperties.first, rTextProperties.second, uno::Any() ); + + // adapt max-extent + awt::Size aCurrSize( xEntry->getSize()); + aResult.Width = std::max( aResult.Width, aCurrSize.Width ); + aResult.Height = std::max( aResult.Height, aCurrSize.Height ); + + rOutTextShapes.push_back( xEntry ); + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + } + + return aResult; +} + +void lcl_collectColumnWidths( std::vector< sal_Int32 >& rColumnWidths, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns, + const std::vector< Reference< drawing::XShape > >& rTextShapes, sal_Int32 nSymbolPlusDistanceWidth ) +{ + rColumnWidths.clear(); + sal_Int32 nNumberOfEntries = rTextShapes.size(); + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow ) + { + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn ) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry < nNumberOfEntries ) + { + awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() ); + sal_Int32 nWidth = nSymbolPlusDistanceWidth + aTextSize.Width; + if( nRow==0 ) + rColumnWidths.push_back( nWidth ); + else + rColumnWidths[nColumn] = std::max( nWidth, rColumnWidths[nColumn] ); + } + } + } +} + +void lcl_collectRowHeighs( std::vector< sal_Int32 >& rRowHeights, const sal_Int32 nNumberOfRows, const sal_Int32 nNumberOfColumns, + const std::vector< Reference< drawing::XShape > >& rTextShapes ) +{ + // calculate maximum height for each row + // and collect column widths + rRowHeights.clear(); + sal_Int32 nNumberOfEntries = rTextShapes.size(); + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow) + { + sal_Int32 nCurrentRowHeight = 0; + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry < nNumberOfEntries ) + { + awt::Size aTextSize( rTextShapes[ nEntry ]->getSize() ); + nCurrentRowHeight = std::max( nCurrentRowHeight, aTextSize.Height ); + } + } + rRowHeights.push_back( nCurrentRowHeight ); + } +} + +sal_Int32 lcl_getTextLineHeight( const std::vector< sal_Int32 >& aRowHeights, const sal_Int32 nNumberOfRows, double fViewFontSize ) +{ + const sal_Int32 nFontHeight = static_cast< sal_Int32 >( fViewFontSize ); + if (!nFontHeight) + return 0; + sal_Int32 nTextLineHeight = nFontHeight; + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow) + { + sal_Int32 nFullTextHeight = aRowHeights[nRow]; + if( ( nFullTextHeight / nFontHeight ) <= 1 ) + { + nTextLineHeight = nFullTextHeight;//found an entry with one line-> have real text height + break; + } + } + return nTextLineHeight; +} + +//returns resulting legend size +awt::Size lcl_placeLegendEntries( + std::vector<ViewLegendEntry> & rEntries, + css::chart::ChartLegendExpansion eExpansion, + bool bSymbolsLeftSide, + double fViewFontSize, + const awt::Size& rMaxSymbolExtent, + tPropertyValues & rTextProperties, + const Reference< drawing::XShapes > & xTarget, + const Reference< lang::XMultiServiceFactory > & xShapeFactory, + const awt::Size& rRemainingSpace, + sal_Int32 nYStartPosition, + const awt::Size& rPageSize, + bool bIsPivotChart, + awt::Size& rDefaultLegendSize) +{ + bool bIsCustomSize = (eExpansion == css::chart::ChartLegendExpansion_CUSTOM); + awt::Size aResultingLegendSize(0,0); + // For Pivot charts set the *minimum* legend size as a function of page size. + if ( bIsPivotChart ) + aResultingLegendSize = awt::Size((rPageSize.Width * 13) / 80, (rPageSize.Height * 31) / 90); + if( bIsCustomSize ) + aResultingLegendSize = awt::Size(rRemainingSpace.Width, rRemainingSpace.Height + nYStartPosition); + + // #i109336# Improve auto positioning in chart + sal_Int32 nXPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.33 ) ); + sal_Int32 nXOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.66 ) ); + sal_Int32 nYPadding = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) ); + sal_Int32 nYOffset = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.2 ) ); + + const sal_Int32 nSymbolToTextDistance = static_cast< sal_Int32 >( std::max( 100.0, fViewFontSize * 0.22 ) );//minimum 1mm + const sal_Int32 nSymbolPlusDistanceWidth = rMaxSymbolExtent.Width + nSymbolToTextDistance; + sal_Int32 nMaxTextWidth = rRemainingSpace.Width - nSymbolPlusDistanceWidth; + uno::Any* pFrameWidthAny = PropertyMapper::getValuePointer( rTextProperties.second, rTextProperties.first, "TextMaximumFrameWidth"); + if(pFrameWidthAny) + { + if( eExpansion == css::chart::ChartLegendExpansion_HIGH ) + { + // limit the width of texts to 30% of the total available width + // #i109336# Improve auto positioning in chart + nMaxTextWidth = rRemainingSpace.Width * 3 / 10; + } + *pFrameWidthAny <<= nMaxTextWidth; + } + + std::vector< Reference< drawing::XShape > > aTextShapes; + awt::Size aMaxEntryExtent = lcl_createTextShapes( rEntries, xShapeFactory, xTarget, aTextShapes, rTextProperties ); + OSL_ASSERT( aTextShapes.size() == rEntries.size()); + + sal_Int32 nMaxEntryWidth = nXOffset + nSymbolPlusDistanceWidth + aMaxEntryExtent.Width; + sal_Int32 nMaxEntryHeight = nYOffset + aMaxEntryExtent.Height; + sal_Int32 nNumberOfEntries = rEntries.size(); + + rDefaultLegendSize.Width = nMaxEntryWidth; + rDefaultLegendSize.Height = nMaxEntryHeight + nYPadding; + + sal_Int32 nNumberOfColumns = 0, nNumberOfRows = 0; + std::vector< sal_Int32 > aColumnWidths; + std::vector< sal_Int32 > aRowHeights; + + sal_Int32 nTextLineHeight = static_cast< sal_Int32 >( fViewFontSize ); + + // determine layout depending on LegendExpansion + if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM ) + { + sal_Int32 nCurrentRow=0; + sal_Int32 nCurrentColumn=-1; + sal_Int32 nMaxColumnCount=-1; + for( sal_Int32 nN=0; nN<static_cast<sal_Int32>(aTextShapes.size()); nN++ ) + { + Reference< drawing::XShape > xShape( aTextShapes[nN] ); + if( !xShape.is() ) + continue; + awt::Size aSize( xShape->getSize() ); + sal_Int32 nNewWidth = aSize.Width + nSymbolPlusDistanceWidth; + sal_Int32 nCurrentColumnCount = aColumnWidths.size(); + + //are we allowed to add a new column? + if( nMaxColumnCount==-1 || (nCurrentColumn+1) < nMaxColumnCount ) + { + //try add a new column + nCurrentColumn++; + if( nCurrentColumn < nCurrentColumnCount ) + { + //check whether the current column width is sufficient for the new entry + if( aColumnWidths[nCurrentColumn]>=nNewWidth ) + { + //all good proceed with next entry + continue; + } + } + if( nCurrentColumn < nCurrentColumnCount ) + aColumnWidths[nCurrentColumn] = std::max( nNewWidth, aColumnWidths[nCurrentColumn] ); + else + aColumnWidths.push_back(nNewWidth); + + //do the columns still fit into the given size? + nCurrentColumnCount = aColumnWidths.size();//update count + sal_Int32 nSumWidth = 0; + for (sal_Int32 nColumn = 0; nColumn < nCurrentColumnCount; nColumn++) + nSumWidth += aColumnWidths[nColumn]; + + if( nSumWidth <= rRemainingSpace.Width || nCurrentColumnCount==1 ) + { + //all good proceed with next entry + continue; + } + else + { + //not enough space for the current amount of columns + //try again with less columns + nMaxColumnCount = nCurrentColumnCount-1; + nN=-1; + nCurrentRow=0; + nCurrentColumn=-1; + aColumnWidths.clear(); + } + } + else + { + //add a new row and try the same entry again + nCurrentRow++; + nCurrentColumn=-1; + nN--; + } + } + nNumberOfColumns = aColumnWidths.size(); + nNumberOfRows = nCurrentRow+1; + + //check if there is not enough space so that some entries must be removed + lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes ); + nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize ); + sal_Int32 nSumHeight = 0; + for (sal_Int32 nRow=0; nRow < nNumberOfRows; nRow++) + nSumHeight += aRowHeights[nRow]; + sal_Int32 nRemainingSpace = rRemainingSpace.Height - nSumHeight; + + if( nRemainingSpace < -100 ) // 1mm tolerance for OOXML interop tdf#90404 + { + //remove entries that are too big + for (sal_Int32 nRow = nNumberOfRows; nRow--; ) + { + for (sal_Int32 nColumn = nNumberOfColumns; nColumn--; ) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry < static_cast<sal_Int32>(aTextShapes.size()) ) + { + DrawModelWrapper::removeShape( aTextShapes[nEntry] ); + aTextShapes.pop_back(); + } + if( nEntry < nNumberOfEntries && ( nEntry != 0 || nNumberOfColumns != 1 ) ) + { + DrawModelWrapper::removeShape( rEntries[ nEntry ].aSymbol ); + rEntries.pop_back(); + nNumberOfEntries--; + } + } + if (nRow == 0 && nNumberOfColumns == 1) + { + try + { + OUString aLabelString = rEntries[0].aLabel[0]->getString(); + const OUString sDots = "..."; + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + for (sal_Int32 nNewLen = aLabelString.getLength() - sDots.getLength(); nNewLen > 0; nNewLen--) + { + OUString aNewLabel = aLabelString.copy(0, nNewLen) + sDots; + Reference<drawing::XShape> xEntry = pShapeFactory->createText( + xTarget, aNewLabel, rTextProperties.first, rTextProperties.second, uno::Any()); + nSumHeight = xEntry->getSize().Height; + nRemainingSpace = rRemainingSpace.Height - nSumHeight; + if (nRemainingSpace >= 0) + { + sal_Int32 nWidth = xEntry->getSize().Width + nSymbolPlusDistanceWidth; + if (rRemainingSpace.Width - nWidth >= 0) + { + aTextShapes.push_back(xEntry); + rEntries[0].aLabel[0]->setString(aNewLabel); + aRowHeights[0] = nSumHeight; + aColumnWidths[0] = nWidth; + break; + } + } + DrawModelWrapper::removeShape(xEntry); + } + if (aTextShapes.size() == 0) + { + DrawModelWrapper::removeShape(rEntries[0].aSymbol); + rEntries.pop_back(); + nNumberOfEntries--; + aRowHeights.pop_back(); + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + } + else + { + nSumHeight -= aRowHeights[nRow]; + aRowHeights.pop_back(); + nRemainingSpace = rRemainingSpace.Height - nSumHeight; + if (nRemainingSpace >= 0) + break; + } + } + nNumberOfRows = static_cast<sal_Int32>(aRowHeights.size()); + } + if( nRemainingSpace >= -100 ) // 1mm tolerance for OOXML interop tdf#90404 + { + sal_Int32 nNormalSpacingHeight = 2*nYPadding+(nNumberOfRows-1)*nYOffset; + if( nRemainingSpace < nNormalSpacingHeight ) + { + //reduce spacing between the entries + nYPadding = nYOffset = nRemainingSpace/(nNumberOfRows+1); + } + else + { + //we have some space left that should be spread equally between all rows + sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingHeight)/(nNumberOfRows+1); + nYPadding += nRemainingSingleSpace; + nYOffset += nRemainingSingleSpace; + } + } + + //check spacing between columns + sal_Int32 nSumWidth = 0; + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; nColumn++) + nSumWidth += aColumnWidths[nColumn]; + nRemainingSpace = rRemainingSpace.Width - nSumWidth; + if( nRemainingSpace>=0 ) + { + sal_Int32 nNormalSpacingWidth = 2*nXPadding+(nNumberOfColumns-1)*nXOffset; + if( nRemainingSpace < nNormalSpacingWidth ) + { + //reduce spacing between the entries + nXPadding = nXOffset = nRemainingSpace/(nNumberOfColumns+1); + } + else + { + //we have some space left that should be spread equally between all columns + sal_Int32 nRemainingSingleSpace = (nRemainingSpace-nNormalSpacingWidth)/(nNumberOfColumns+1); + nXPadding += nRemainingSingleSpace; + nXOffset += nRemainingSingleSpace; + } + } + } + else if( eExpansion == css::chart::ChartLegendExpansion_HIGH ) + { + sal_Int32 nMaxNumberOfRows = nMaxEntryHeight + ? (rRemainingSpace.Height - 2*nYPadding ) / nMaxEntryHeight + : 0; + + nNumberOfColumns = nMaxNumberOfRows + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nMaxNumberOfRows ) )) + : 0; + nNumberOfRows = nNumberOfColumns + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nNumberOfColumns ) )) + : 0; + } + else if( eExpansion == css::chart::ChartLegendExpansion_WIDE ) + { + sal_Int32 nMaxNumberOfColumns = nMaxEntryWidth + ? (rRemainingSpace.Width - 2*nXPadding ) / nMaxEntryWidth + : 0; + + nNumberOfRows = nMaxNumberOfColumns + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nMaxNumberOfColumns ) )) + : 0; + nNumberOfColumns = nNumberOfRows + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nNumberOfRows ) )) + : 0; + } + else // css::chart::ChartLegendExpansion_BALANCED + { + double fAspect = nMaxEntryHeight + ? static_cast< double >( nMaxEntryWidth ) / static_cast< double >( nMaxEntryHeight ) + : 0.0; + + nNumberOfRows = static_cast< sal_Int32 >( + ceil( sqrt( static_cast< double >( nNumberOfEntries ) * fAspect ))); + nNumberOfColumns = nNumberOfRows + ? static_cast< sal_Int32 >( + ceil( static_cast< double >( nNumberOfEntries ) / + static_cast< double >( nNumberOfRows ) )) + : 0; + } + + if(nNumberOfRows<=0) + return aResultingLegendSize; + + if( eExpansion != css::chart::ChartLegendExpansion_CUSTOM ) + { + lcl_collectColumnWidths( aColumnWidths, nNumberOfRows, nNumberOfColumns, aTextShapes, nSymbolPlusDistanceWidth ); + lcl_collectRowHeighs( aRowHeights, nNumberOfRows, nNumberOfColumns, aTextShapes ); + nTextLineHeight = lcl_getTextLineHeight( aRowHeights, nNumberOfRows, fViewFontSize ); + } + + sal_Int32 nCurrentXPos = bSymbolsLeftSide ? nXPadding : -nXPadding; + + // place entries into column and rows + sal_Int32 nMaxYPos = 0; + + for (sal_Int32 nColumn = 0; nColumn < nNumberOfColumns; ++nColumn) + { + sal_Int32 nCurrentYPos = nYPadding + nYStartPosition; + for (sal_Int32 nRow = 0; nRow < nNumberOfRows; ++nRow) + { + sal_Int32 nEntry = nColumn + nRow * nNumberOfColumns; + if( nEntry >= nNumberOfEntries ) + break; + + // text shape + Reference< drawing::XShape > xTextShape( aTextShapes[nEntry] ); + if( xTextShape.is() ) + { + awt::Size aTextSize( xTextShape->getSize() ); + sal_Int32 nTextXPos = nCurrentXPos + nSymbolPlusDistanceWidth; + if( !bSymbolsLeftSide ) + nTextXPos = nCurrentXPos - nSymbolPlusDistanceWidth - aTextSize.Width; + xTextShape->setPosition( awt::Point( nTextXPos, nCurrentYPos )); + } + + // symbol + Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol ); + if( xSymbol.is() ) + { + awt::Size aSymbolSize( rMaxSymbolExtent ); + sal_Int32 nSymbolXPos = nCurrentXPos; + if( !bSymbolsLeftSide ) + nSymbolXPos = nCurrentXPos - rMaxSymbolExtent.Width; + sal_Int32 nSymbolYPos = nCurrentYPos + ( ( nTextLineHeight - aSymbolSize.Height ) / 2 ); + xSymbol->setPosition( awt::Point( nSymbolXPos, nSymbolYPos ) ); + } + + nCurrentYPos += aRowHeights[ nRow ]; + if( nRow+1 < nNumberOfRows ) + nCurrentYPos += nYOffset; + nMaxYPos = std::max( nMaxYPos, nCurrentYPos ); + } + if( bSymbolsLeftSide ) + { + nCurrentXPos += aColumnWidths[nColumn]; + if( nColumn+1 < nNumberOfColumns ) + nCurrentXPos += nXOffset; + } + else + { + nCurrentXPos -= aColumnWidths[nColumn]; + if( nColumn+1 < nNumberOfColumns ) + nCurrentXPos -= nXOffset; + } + } + + if( !bIsCustomSize ) + { + if( bSymbolsLeftSide ) + aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nCurrentXPos + nXPadding ); + else + { + sal_Int32 nLegendWidth = -(nCurrentXPos-nXPadding); + aResultingLegendSize.Width = std::max( aResultingLegendSize.Width, nLegendWidth ); + } + aResultingLegendSize.Height = std::max( aResultingLegendSize.Height, nMaxYPos + nYPadding ); + } + + if( !bSymbolsLeftSide ) + { + sal_Int32 nLegendWidth = aResultingLegendSize.Width; + awt::Point aPos(0,0); + for( sal_Int32 nEntry=0; nEntry<nNumberOfEntries; nEntry++ ) + { + Reference< drawing::XShape > xSymbol( rEntries[ nEntry ].aSymbol ); + aPos = xSymbol->getPosition(); + aPos.X += nLegendWidth; + xSymbol->setPosition( aPos ); + Reference< drawing::XShape > xText( aTextShapes[ nEntry ] ); + aPos = xText->getPosition(); + aPos.X += nLegendWidth; + xText->setPosition( aPos ); + } + } + + return aResultingLegendSize; +} + +// #i109336# Improve auto positioning in chart +sal_Int32 lcl_getLegendLeftRightMargin() +{ + return 210; // 1/100 mm +} + +// #i109336# Improve auto positioning in chart +sal_Int32 lcl_getLegendTopBottomMargin() +{ + return 185; // 1/100 mm +} + +chart2::RelativePosition lcl_getDefaultPosition( LegendPosition ePos, const awt::Rectangle& rOutAvailableSpace, const awt::Size & rPageSize ) +{ + chart2::RelativePosition aResult; + + switch( ePos ) + { + case LegendPosition_LINE_START: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) / + static_cast< double >( rPageSize.Width ); + aResult = chart2::RelativePosition( + fDefaultDistance, 0.5, drawing::Alignment_LEFT ); + } + break; + case LegendPosition_LINE_END: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendLeftRightMargin() ) / + static_cast< double >( rPageSize.Width ); + aResult = chart2::RelativePosition( + 1.0 - fDefaultDistance, 0.5, drawing::Alignment_RIGHT ); + } + break; + case LegendPosition_PAGE_START: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) / + static_cast< double >( rPageSize.Height ); + double fDistance = (static_cast<double>(rOutAvailableSpace.Y)/static_cast<double>(rPageSize.Height)) + fDefaultDistance; + aResult = chart2::RelativePosition( + 0.5, fDistance, drawing::Alignment_TOP ); + } + break; + case LegendPosition_PAGE_END: + { + // #i109336# Improve auto positioning in chart + const double fDefaultDistance = static_cast< double >( lcl_getLegendTopBottomMargin() ) / + static_cast< double >( rPageSize.Height ); + + double fDistance = double(rPageSize.Height - (rOutAvailableSpace.Y + rOutAvailableSpace.Height)); + fDistance += fDefaultDistance; + fDistance /= double(rPageSize.Height); + + aResult = chart2::RelativePosition( + 0.5, 1.0 - fDistance, drawing::Alignment_BOTTOM ); + } + break; + case LegendPosition::LegendPosition_MAKE_FIXED_SIZE: + default: + // nothing to be set + break; + } + + return aResult; +} + +/** @return + a point relative to the upper left corner that can be used for + XShape::setPosition() +*/ +awt::Point lcl_calculatePositionAndRemainingSpace( + awt::Rectangle & rRemainingSpace, + const awt::Size & rPageSize, + const chart2::RelativePosition& rRelPos, + LegendPosition ePos, + const awt::Size& aLegendSize, + bool bOverlay ) +{ + // calculate position + awt::Point aResult( + static_cast< sal_Int32 >( rRelPos.Primary * rPageSize.Width ), + static_cast< sal_Int32 >( rRelPos.Secondary * rPageSize.Height )); + + aResult = RelativePositionHelper::getUpperLeftCornerOfAnchoredObject( + aResult, aLegendSize, rRelPos.Anchor ); + + // adapt rRemainingSpace if LegendPosition is not CUSTOM + // #i109336# Improve auto positioning in chart + sal_Int32 nXDistance = lcl_getLegendLeftRightMargin(); + sal_Int32 nYDistance = lcl_getLegendTopBottomMargin(); + if (!bOverlay) switch( ePos ) + { + case LegendPosition_LINE_START: + { + sal_Int32 nExtent = aLegendSize.Width; + rRemainingSpace.Width -= ( nExtent + nXDistance ); + rRemainingSpace.X += ( nExtent + nXDistance ); + } + break; + case LegendPosition_LINE_END: + { + rRemainingSpace.Width -= ( aLegendSize.Width + nXDistance ); + } + break; + case LegendPosition_PAGE_START: + { + sal_Int32 nExtent = aLegendSize.Height; + rRemainingSpace.Height -= ( nExtent + nYDistance ); + rRemainingSpace.Y += ( nExtent + nYDistance ); + } + break; + case LegendPosition_PAGE_END: + { + rRemainingSpace.Height -= ( aLegendSize.Height + nYDistance ); + } + break; + + default: + // nothing + break; + } + + // adjust the legend position. Esp. for old files that had slightly smaller legends + const sal_Int32 nEdgeDistance( 30 ); + if( aResult.X + aLegendSize.Width > rPageSize.Width ) + { + sal_Int32 nNewX( (rPageSize.Width - aLegendSize.Width) - nEdgeDistance ); + if( nNewX > rPageSize.Width / 4 ) + aResult.X = nNewX; + } + if( aResult.Y + aLegendSize.Height > rPageSize.Height ) + { + sal_Int32 nNewY( (rPageSize.Height - aLegendSize.Height) - nEdgeDistance ); + if( nNewY > rPageSize.Height / 4 ) + aResult.Y = nNewY; + } + + return aResult; +} + +bool lcl_shouldSymbolsBePlacedOnTheLeftSide( const Reference< beans::XPropertySet >& xLegendProp, sal_Int16 nDefaultWritingMode ) +{ + bool bSymbolsLeftSide = true; + try + { + if( SvtLanguageOptions().IsCTLFontEnabled() ) + { + if(xLegendProp.is()) + { + sal_Int16 nWritingMode=-1; + if( xLegendProp->getPropertyValue( "WritingMode" ) >>= nWritingMode ) + { + if( nWritingMode == text::WritingMode2::PAGE ) + nWritingMode = nDefaultWritingMode; + if( nWritingMode == text::WritingMode2::RL_TB ) + bSymbolsLeftSide=false; + } + } + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + return bSymbolsLeftSide; +} + +std::vector<std::shared_ptr<VButton>> lcl_createButtons( + uno::Reference<drawing::XShapes> const & xLegendContainer, + uno::Reference<lang::XMultiServiceFactory> const & xShapeFactory, + ChartModel& rModel, bool bPlaceButtonsVertically, long & nUsedHeight) +{ + std::vector<std::shared_ptr<VButton>> aButtons; + + uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider(rModel.getDataProvider(), uno::UNO_QUERY); + if (!xPivotTableDataProvider.is()) + return aButtons; + + if (!xPivotTableDataProvider->getColumnFields().hasElements()) + return aButtons; + + awt::Size aSize(2000, 700); + int x = 100; + int y = 100; + + const css::uno::Sequence<chart2::data::PivotTableFieldEntry> aPivotFieldEntries = xPivotTableDataProvider->getColumnFields(); + for (chart2::data::PivotTableFieldEntry const & sColumnFieldEntry : aPivotFieldEntries) + { + auto pButton = std::make_shared<VButton>(); + aButtons.push_back(pButton); + pButton->init(xLegendContainer, xShapeFactory); + awt::Point aNewPosition(x, y); + pButton->setLabel(sColumnFieldEntry.Name); + pButton->setCID("FieldButton.Column." + OUString::number(sColumnFieldEntry.DimensionIndex)); + pButton->setPosition(aNewPosition); + pButton->setSize(aSize); + if (sColumnFieldEntry.Name == "Data") + { + pButton->showArrow(false); + pButton->setBGColor(Color(0x00F6F6F6)); + } + if (sColumnFieldEntry.HasHiddenMembers) + pButton->setArrowColor(Color(0x0000FF)); + + if (bPlaceButtonsVertically) + y += aSize.Height + 100; + else + x += aSize.Width + 100; + } + if (bPlaceButtonsVertically) + nUsedHeight += y + 100; + else + nUsedHeight += aSize.Height + 100; + + return aButtons; +} + +} // anonymous namespace + +VLegend::VLegend( + const Reference< XLegend > & xLegend, + const Reference< uno::XComponentContext > & xContext, + const std::vector< LegendEntryProvider* >& rLegendEntryProviderList, + const Reference< drawing::XShapes >& xTargetPage, + const Reference< lang::XMultiServiceFactory >& xFactory, + ChartModel& rModel ) + : m_xTarget(xTargetPage) + , m_xShapeFactory(xFactory) + , m_xLegend(xLegend) + , mrModel(rModel) + , m_xContext(xContext) + , m_aLegendEntryProviderList(rLegendEntryProviderList) + , m_nDefaultWritingMode(text::WritingMode2::LR_TB) +{ +} + +void VLegend::setDefaultWritingMode( sal_Int16 nDefaultWritingMode ) +{ + m_nDefaultWritingMode = nDefaultWritingMode; +} + +bool VLegend::isVisible( const Reference< XLegend > & xLegend ) +{ + if( ! xLegend.is()) + return false; + + bool bShow = false; + try + { + Reference< beans::XPropertySet > xLegendProp( xLegend, uno::UNO_QUERY_THROW ); + xLegendProp->getPropertyValue( "Show") >>= bShow; + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + + return bShow; +} + +void VLegend::createShapes( + const awt::Size & rAvailableSpace, + const awt::Size & rPageSize, + awt::Size & rDefaultLegendSize ) +{ + if(! (m_xLegend.is() && + m_xShapeFactory.is() && + m_xTarget.is())) + return; + + try + { + //create shape and add to page + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory); + OUString aLegendParticle( ObjectIdentifier::createParticleForLegend( mrModel ) ); + m_xShape.set( pShapeFactory->createGroup2D( m_xTarget, + ObjectIdentifier::createClassifiedIdentifierForParticle( aLegendParticle )), + uno::UNO_QUERY); + + // create and insert sub-shapes + Reference< drawing::XShapes > xLegendContainer( m_xShape, uno::UNO_QUERY ); + if( xLegendContainer.is()) + { + // for quickly setting properties + tPropertyValues aLineFillProperties; + tPropertyValues aTextProperties; + + Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY ); + css::chart::ChartLegendExpansion eExpansion = css::chart::ChartLegendExpansion_HIGH; + awt::Size aLegendSize( rAvailableSpace ); + + bool bCustom = false; + LegendPosition eLegendPosition = LegendPosition_LINE_END; + if (xLegendProp.is()) + { + // get Expansion property + xLegendProp->getPropertyValue("Expansion") >>= eExpansion; + if( eExpansion == css::chart::ChartLegendExpansion_CUSTOM ) + { + RelativeSize aRelativeSize; + if (xLegendProp->getPropertyValue("RelativeSize") >>= aRelativeSize) + { + aLegendSize.Width = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Primary * rPageSize.Width )); + aLegendSize.Height = static_cast<sal_Int32>(::rtl::math::approxCeil( aRelativeSize.Secondary * rPageSize.Height )); + bCustom = true; + } + else + { + eExpansion = css::chart::ChartLegendExpansion_HIGH; + } + } + xLegendProp->getPropertyValue("AnchorPosition") >>= eLegendPosition; + lcl_getProperties( xLegendProp, aLineFillProperties, aTextProperties, rPageSize ); + } + + // create entries + double fViewFontSize = lcl_CalcViewFontSize( xLegendProp, rPageSize );//todo + // #i109336# Improve auto positioning in chart + sal_Int32 nSymbolHeight = static_cast< sal_Int32 >( fViewFontSize * 0.6 ); + sal_Int32 nSymbolWidth = nSymbolHeight; + + for (LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList) + { + if (pLegendEntryProvider) + { + awt::Size aCurrentRatio = pLegendEntryProvider->getPreferredLegendKeyAspectRatio(); + sal_Int32 nCurrentWidth = aCurrentRatio.Width; + if( aCurrentRatio.Height > 0 ) + { + nCurrentWidth = nSymbolHeight* aCurrentRatio.Width/aCurrentRatio.Height; + } + nSymbolWidth = std::max( nSymbolWidth, nCurrentWidth ); + } + } + awt::Size aMaxSymbolExtent( nSymbolWidth, nSymbolHeight ); + + std::vector<ViewLegendEntry> aViewEntries; + for(LegendEntryProvider* pLegendEntryProvider : m_aLegendEntryProviderList) + { + if (pLegendEntryProvider) + { + std::vector<ViewLegendEntry> aNewEntries = pLegendEntryProvider->createLegendEntries( + aMaxSymbolExtent, eLegendPosition, xLegendProp, + xLegendContainer, m_xShapeFactory, m_xContext, mrModel); + if (aNewEntries.size() == 0) + return; + aViewEntries.insert( aViewEntries.end(), aNewEntries.begin(), aNewEntries.end() ); + } + } + + bool bSymbolsLeftSide = lcl_shouldSymbolsBePlacedOnTheLeftSide( xLegendProp, m_nDefaultWritingMode ); + + uno::Reference<chart2::data::XPivotTableDataProvider> xPivotTableDataProvider( mrModel.getDataProvider(), uno::UNO_QUERY ); + bool bIsPivotChart = xPivotTableDataProvider.is(); + + if ( !aViewEntries.empty() || bIsPivotChart ) + { + // create buttons + long nUsedButtonHeight = 0; + bool bPlaceButtonsVertically = (eLegendPosition != LegendPosition_PAGE_START && + eLegendPosition != LegendPosition_PAGE_END && + eExpansion != css::chart::ChartLegendExpansion_WIDE); + + std::vector<std::shared_ptr<VButton>> aButtons = lcl_createButtons(xLegendContainer, m_xShapeFactory, mrModel, bPlaceButtonsVertically, nUsedButtonHeight); + + // A custom size includes the size we used for buttons already, so we need to + // subtract that from the size that is available for the legend + if (bCustom) + aLegendSize.Height -= nUsedButtonHeight; + + // place the legend entries + aLegendSize = lcl_placeLegendEntries(aViewEntries, eExpansion, bSymbolsLeftSide, fViewFontSize, + aMaxSymbolExtent, aTextProperties, xLegendContainer, + m_xShapeFactory, aLegendSize, nUsedButtonHeight, rPageSize, bIsPivotChart, rDefaultLegendSize); + + uno::Reference<beans::XPropertySet> xModelPage(mrModel.getPageBackground()); + + for (std::shared_ptr<VButton> const & pButton : aButtons) + { + // adjust the width of the buttons if we place them vertically + if (bPlaceButtonsVertically) + pButton->setSize({aLegendSize.Width - 200, pButton->getSize().Height}); + + // create the buttons + pButton->createShapes(xModelPage); + } + } + + Reference< drawing::XShape > xBorder = + pShapeFactory->createRectangle( xLegendContainer, + aLegendSize, + awt::Point(0,0), + aLineFillProperties.first, + aLineFillProperties.second, ShapeFactory::StackPosition::Bottom ); + + //because of this name this border will be used for marking the legend + ShapeFactory::setShapeName( xBorder, "MarkHandles" ); + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + } +} + +void VLegend::changePosition( + awt::Rectangle & rOutAvailableSpace, + const awt::Size & rPageSize, + const css::awt::Size & rDefaultLegendSize ) +{ + if(! m_xShape.is()) + return; + + try + { + // determine position and alignment depending on default position + awt::Size aLegendSize = m_xShape->getSize(); + Reference< beans::XPropertySet > xLegendProp( m_xLegend, uno::UNO_QUERY_THROW ); + chart2::RelativePosition aRelativePosition; + + bool bDefaultLegendSize = rDefaultLegendSize.Width != 0 || rDefaultLegendSize.Height != 0; + bool bAutoPosition = + ! (xLegendProp->getPropertyValue( "RelativePosition") >>= aRelativePosition); + + LegendPosition ePos = LegendPosition_LINE_END; + xLegendProp->getPropertyValue( "AnchorPosition") >>= ePos; + + bool bOverlay = false; + xLegendProp->getPropertyValue("Overlay") >>= bOverlay; + //calculate position + if( bAutoPosition ) + { + // auto position: relative to remaining space + aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize ); + awt::Point aPos = lcl_calculatePositionAndRemainingSpace( + rOutAvailableSpace, rPageSize, aRelativePosition, ePos, aLegendSize, bOverlay ); + m_xShape->setPosition( aPos ); + } + else + { + // manual position: relative to whole page + awt::Rectangle aAvailableSpace( 0, 0, rPageSize.Width, rPageSize.Height ); + awt::Point aPos = lcl_calculatePositionAndRemainingSpace( + aAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay ); + m_xShape->setPosition( aPos ); + + if (!bOverlay) + { + // calculate remaining space as if having autoposition: + aRelativePosition = lcl_getDefaultPosition( ePos, rOutAvailableSpace, rPageSize ); + lcl_calculatePositionAndRemainingSpace( + rOutAvailableSpace, rPageSize, aRelativePosition, ePos, bDefaultLegendSize ? rDefaultLegendSize : aLegendSize, bOverlay ); + } + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2" ); + } +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VLegend.hxx b/chart2/source/view/main/VLegend.hxx new file mode 100644 index 000000000..297a99129 --- /dev/null +++ b/chart2/source/view/main/VLegend.hxx @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_CHART2_SOURCE_VIEW_MAIN_VLEGEND_HXX +#define INCLUDED_CHART2_SOURCE_VIEW_MAIN_VLEGEND_HXX + +#include <com/sun/star/uno/Reference.hxx> + +#include <vector> + +namespace chart { class ChartModel; } +namespace com::sun::star::awt { struct Rectangle; } +namespace com::sun::star::awt { struct Size; } +namespace com::sun::star::chart2 { class XLegend; } +namespace com::sun::star::drawing { class XShape; } +namespace com::sun::star::drawing { class XShapes; } +namespace com::sun::star::lang { class XMultiServiceFactory; } +namespace com::sun::star::uno { class XComponentContext; } + +namespace chart +{ + +class LegendEntryProvider; + +class VLegend +{ +public: + VLegend( const css::uno::Reference< css::chart2::XLegend > & xLegend, + const css::uno::Reference< css::uno::XComponentContext > & xContext, + const std::vector< LegendEntryProvider* >& rLegendEntryProviderList, + const css::uno::Reference< css::drawing::XShapes >& xTargetPage, + const css::uno::Reference< css::lang::XMultiServiceFactory >& xFactory, + ChartModel& rModel ); + + void setDefaultWritingMode( sal_Int16 nDefaultWritingMode ); + + void createShapes( const css::awt::Size & rAvailableSpace, + const css::awt::Size & rPageSize, + css::awt::Size & rDefaultLegendSize ); + + /** Sets the position according to its internal anchor. + + @param rOutAvailableSpace + is modified by the method, if the legend is in a standard position, + such that the space allocated by the legend is removed from it. + + @param rReferenceSize + is used to calculate the offset (default 2%) from the edge. + */ + void changePosition( + css::awt::Rectangle & rOutAvailableSpace, + const css::awt::Size & rReferenceSize, + const css::awt::Size & rDefaultLegendSize ); + + static bool isVisible( + const css::uno::Reference< css::chart2::XLegend > & xLegend ); + +private: + css::uno::Reference< css::drawing::XShapes > m_xTarget; + css::uno::Reference< css::lang::XMultiServiceFactory> m_xShapeFactory; + css::uno::Reference< css::chart2::XLegend > m_xLegend; + css::uno::Reference< css::drawing::XShape > m_xShape; + + ChartModel& mrModel; + + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + std::vector< LegendEntryProvider* > m_aLegendEntryProviderList; + + sal_Int16 m_nDefaultWritingMode;//to be used when writing mode is set to page +}; + +} //namespace chart +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VLegendSymbolFactory.cxx b/chart2/source/view/main/VLegendSymbolFactory.cxx new file mode 100644 index 000000000..1ccc0de34 --- /dev/null +++ b/chart2/source/view/main/VLegendSymbolFactory.cxx @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <VLegendSymbolFactory.hxx> +#include <PropertyMapper.hxx> +#include <ShapeFactory.hxx> +#include <com/sun/star/drawing/Position3D.hpp> +#include <com/sun/star/chart2/Symbol.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/Direction3D.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Reference; + +namespace +{ + +void getPropNamesAndValues( const Reference< beans::XPropertySet >& xProp, + ::chart::tNameSequence& rNames, + ::chart::tAnySequence& rValues, + ::chart::VLegendSymbolFactory::PropertyType ePropertyType, + const awt::Size& aMaxSymbolExtent) +{ + const ::chart::tPropertyNameMap & aFilledSeriesNameMap( ::chart::PropertyMapper::getPropertyNameMapForFilledSeriesProperties()); + const ::chart::tPropertyNameMap & aLineSeriesNameMap( ::chart::PropertyMapper::getPropertyNameMapForLineSeriesProperties()); + const ::chart::tPropertyNameMap & aLineNameMap( ::chart::PropertyMapper::getPropertyNameMapForLineProperties()); + + ::chart::tPropertyNameValueMap aValueMap; + switch( ePropertyType ) + { + case ::chart::VLegendSymbolFactory::PropertyType::FilledSeries: + ::chart::PropertyMapper::getValueMap( aValueMap, aFilledSeriesNameMap, xProp ); + break; + case ::chart::VLegendSymbolFactory::PropertyType::LineSeries: + ::chart::PropertyMapper::getValueMap( aValueMap, aLineSeriesNameMap, xProp ); + break; + case ::chart::VLegendSymbolFactory::PropertyType::Line: + ::chart::PropertyMapper::getValueMap( aValueMap, aLineNameMap, xProp ); + break; + } + + ::chart::PropertyMapper::getMultiPropertyListsFromValueMap( rNames, rValues, aValueMap ); + + uno::Any* pLineWidthAny = ::chart::PropertyMapper::getValuePointer(rValues,rNames,"LineWidth"); + sal_Int32 nLineWidth = 0; + if( pLineWidthAny && (*pLineWidthAny>>=nLineWidth) ) + { + // use legend entry height as upper limit for line width + sal_Int32 nMaxLineWidthForLegend = aMaxSymbolExtent.Height; + if( nLineWidth>nMaxLineWidthForLegend ) + *pLineWidthAny <<= nMaxLineWidthForLegend; + } +} + +void lcl_setPropertiesToShape( + const Reference< beans::XPropertySet > & xProp, + const Reference< drawing::XShape > & xShape, + ::chart::VLegendSymbolFactory::PropertyType ePropertyType, + const awt::Size& aMaxSymbolExtent) +{ + ::chart::tNameSequence aPropNames; + ::chart::tAnySequence aPropValues; + getPropNamesAndValues( xProp, aPropNames, aPropValues, + ePropertyType, aMaxSymbolExtent ); + + Reference< beans::XPropertySet > xShapeProp( xShape, uno::UNO_QUERY ); + ::chart::PropertyMapper::setMultiProperties( aPropNames, aPropValues, xShapeProp ); +} + +} // anonymous namespace + +namespace chart +{ + +Reference< drawing::XShape > VLegendSymbolFactory::createSymbol( + const awt::Size& rEntryKeyAspectRatio, + const Reference< drawing::XShapes >& rSymbolContainer, + LegendSymbolStyle eStyle, + const Reference< lang::XMultiServiceFactory > & xShapeFactory, + const Reference< beans::XPropertySet > & xLegendEntryProperties, + PropertyType ePropertyType, const uno::Any& rExplicitSymbol ) +{ + Reference< drawing::XShape > xResult; + + if( ! (rSymbolContainer.is() && xShapeFactory.is())) + return xResult; + + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(xShapeFactory); + xResult.set( pShapeFactory->createGroup2D( rSymbolContainer ), uno::UNO_QUERY ); + + Reference< drawing::XShapes > xResultGroup( xResult, uno::UNO_QUERY ); + if( ! xResultGroup.is()) + return xResult; + + // add an invisible square box to maintain aspect ratio + pShapeFactory->createInvisibleRectangle( xResultGroup, rEntryKeyAspectRatio ); + + // create symbol + try + { + if( eStyle == LegendSymbolStyle::Line ) + { + Reference< drawing::XShape > xLine = + pShapeFactory->createLine( xResultGroup, awt::Size( rEntryKeyAspectRatio.Width, 0 ), + awt::Point( 0, rEntryKeyAspectRatio.Height/2 )); + if( xLine.is()) + { + lcl_setPropertiesToShape( xLegendEntryProperties, xLine, ePropertyType, rEntryKeyAspectRatio ); + } + + Reference< drawing::XShape > xSymbol; + const sal_Int32 nSize = std::min(rEntryKeyAspectRatio.Width,rEntryKeyAspectRatio.Height); + chart2::Symbol aSymbol; + if( rExplicitSymbol >>= aSymbol ) + { + drawing::Direction3D aSymbolSize( nSize, nSize, 0 ); + drawing::Position3D aPos( rEntryKeyAspectRatio.Width/2.0, rEntryKeyAspectRatio.Height/2.0, 0 ); + ShapeFactory* pFactory = ShapeFactory::getOrCreateShapeFactory( xShapeFactory ); + if( aSymbol.Style == chart2::SymbolStyle_STANDARD ) + { + // take series color as fill color + xLegendEntryProperties->getPropertyValue( "Color") >>= aSymbol.FillColor; + // border of symbols always same as fill color + aSymbol.BorderColor = aSymbol.FillColor; + + xSymbol.set( pFactory->createSymbol2D( + xResultGroup, + aPos, + aSymbolSize, + aSymbol.StandardSymbol, + aSymbol.BorderColor, + aSymbol.FillColor )); + } + else if( aSymbol.Style == chart2::SymbolStyle_GRAPHIC ) + { + xSymbol.set( pFactory->createGraphic2D( + xResultGroup, + aPos, + aSymbolSize, + aSymbol.Graphic )); + } + else if( aSymbol.Style == chart2::SymbolStyle_AUTO ) + { + SAL_WARN("chart2", "the given parameter is not allowed to contain an automatic symbol style"); + } + } + } + else if( eStyle == LegendSymbolStyle::Circle ) + { + sal_Int32 nSize = std::min( rEntryKeyAspectRatio.Width, rEntryKeyAspectRatio.Height ); + Reference< drawing::XShape > xShape = + pShapeFactory->createCircle( xResultGroup, awt::Size( nSize, nSize ), + awt::Point( rEntryKeyAspectRatio.Width/2-nSize/2, rEntryKeyAspectRatio.Height/2-nSize/2 )); + if( xShape.is() ) + { + lcl_setPropertiesToShape( xLegendEntryProperties, xShape, ePropertyType, awt::Size(0,0) ); // PropertyType::FilledSeries ); + } + } + else // eStyle == LegendSymbolStyle::Box + { + tNameSequence aPropNames; + tAnySequence aPropValues; + + getPropNamesAndValues( xLegendEntryProperties, aPropNames, aPropValues, + ePropertyType, awt::Size(0,0) );// PropertyType::FilledSeries + + pShapeFactory->createRectangle( xResultGroup, + rEntryKeyAspectRatio, awt::Point( 0, 0 ), + aPropNames, aPropValues ); + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + + return xResult; +} + +} // namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VLineProperties.cxx b/chart2/source/view/main/VLineProperties.cxx new file mode 100644 index 000000000..e86c5f7b8 --- /dev/null +++ b/chart2/source/view/main/VLineProperties.cxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <VLineProperties.hxx> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/LineCap.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <tools/diagnose_ex.h> + +namespace chart +{ +using namespace ::com::sun::star; + +// get line properties from a propertyset + +VLineProperties::VLineProperties() +{ + Color <<= sal_Int32(0x000000); //type sal_Int32 UNO_NAME_LINECOLOR + LineStyle <<= drawing::LineStyle_SOLID; //type drawing::LineStyle for property UNO_NAME_LINESTYLE + Transparence <<= sal_Int16(0);//type sal_Int16 for property UNO_NAME_LINETRANSPARENCE + Width <<= sal_Int32(0);//type sal_Int32 for property UNO_NAME_LINEWIDTH + LineCap <<= drawing::LineCap_BUTT; //type drawing::LineCap for property UNO_NAME_LINECAP +} + +void VLineProperties::initFromPropertySet( const uno::Reference< beans::XPropertySet >& xProp ) +{ + if(xProp.is()) + { + try + { + Color = xProp->getPropertyValue( "LineColor" ); + LineStyle = xProp->getPropertyValue( "LineStyle" ); + Transparence = xProp->getPropertyValue( "LineTransparence" ); + Width = xProp->getPropertyValue( "LineWidth" ); + DashName = xProp->getPropertyValue( "LineDashName" ); + LineCap = xProp->getPropertyValue( "LineCap" ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + } + else + LineStyle <<= drawing::LineStyle_NONE; +} + +bool VLineProperties::isLineVisible() const +{ + bool bRet = false; + + drawing::LineStyle aLineStyle(drawing::LineStyle_SOLID); + LineStyle >>= aLineStyle; + if( aLineStyle != drawing::LineStyle_NONE ) + { + sal_Int16 nLineTransparence=0; + Transparence >>= nLineTransparence; + if(nLineTransparence!=100) + { + bRet = true; + } + } + + return bRet; +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VPolarTransformation.cxx b/chart2/source/view/main/VPolarTransformation.cxx new file mode 100644 index 000000000..6d3ba587f --- /dev/null +++ b/chart2/source/view/main/VPolarTransformation.cxx @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <VPolarTransformation.hxx> +#include <CommonConverters.hxx> + +using namespace ::com::sun::star; + +using ::com::sun::star::uno::Sequence; + +namespace chart +{ + +VPolarTransformation::VPolarTransformation( const PolarPlottingPositionHelper& rPositionHelper ) + : m_aPositionHelper(rPositionHelper) + , m_aUnitCartesianToScene( rPositionHelper.getUnitCartesianToScene() ) +{ +} + +VPolarTransformation::~VPolarTransformation() +{ +} + +// ____ XTransformation ____ +Sequence< double > SAL_CALL VPolarTransformation::transform( + const Sequence< double >& rSourceValues ) +{ + double fScaledLogicAngle = rSourceValues[0]; + double fScaledLogicRadius = rSourceValues[1]; + + if( m_aPositionHelper.isSwapXAndY() ) + std::swap(fScaledLogicAngle,fScaledLogicRadius); + + double fAngleDegree = m_aPositionHelper.transformToAngleDegree( fScaledLogicAngle, false ); + double fAnglePi = basegfx::deg2rad(fAngleDegree); + double fRadius = m_aPositionHelper.transformToRadius( fScaledLogicRadius, false); + + double fX=fRadius*cos(fAnglePi); + double fY=fRadius*sin(fAnglePi); + double fZ=rSourceValues[2]; + + //!! applying matrix to vector does ignore translation, so it is important to use a B3DPoint here instead of B3DVector + ::basegfx::B3DPoint aPoint(fX,fY,fZ); + ::basegfx::B3DPoint aRet = m_aUnitCartesianToScene * aPoint; + return B3DPointToSequence(aRet); +} + +sal_Int32 SAL_CALL VPolarTransformation::getSourceDimension() +{ + return 3; +} + +sal_Int32 SAL_CALL VPolarTransformation::getTargetDimension() +{ + return 3; +} + +} // namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VTitle.cxx b/chart2/source/view/main/VTitle.cxx new file mode 100644 index 000000000..53e214f76 --- /dev/null +++ b/chart2/source/view/main/VTitle.cxx @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "VTitle.hxx" +#include <CommonConverters.hxx> +#include <ShapeFactory.hxx> +#include <com/sun/star/chart2/XTitle.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <tools/diagnose_ex.h> + +namespace chart +{ +using namespace ::com::sun::star; +using namespace ::com::sun::star::chart2; + +VTitle::VTitle( const uno::Reference< XTitle > & xTitle ) + : m_xTitle(xTitle) + , m_aCID() + , m_fRotationAngleDegree(0.0) + , m_nXPos(0) + , m_nYPos(0) +{ +} + +VTitle::~VTitle() +{ +} + +void VTitle::init( + const uno::Reference< drawing::XShapes >& xTargetPage + , const uno::Reference< lang::XMultiServiceFactory >& xFactory + , const OUString& rCID ) +{ + m_xTarget = xTargetPage; + m_xShapeFactory = xFactory; + m_aCID = rCID; +} + +double VTitle::getRotationAnglePi() const +{ + return basegfx::deg2rad(m_fRotationAngleDegree); +} + +awt::Size VTitle::getUnrotatedSize() const //size before rotation +{ + awt::Size aRet; + if(m_xShape.is()) + aRet = m_xShape->getSize(); + return aRet; +} + +awt::Size VTitle::getFinalSize() const //size after rotation +{ + return ShapeFactory::getSizeAfterRotation( + m_xShape, m_fRotationAngleDegree ); +} + +void VTitle::changePosition( const awt::Point& rPos ) +{ + if(!m_xShape.is()) + return; + uno::Reference< beans::XPropertySet > xShapeProp( m_xShape, uno::UNO_QUERY ); + if(!xShapeProp.is()) + return; + try + { + m_nXPos = rPos.X; + m_nYPos = rPos.Y; + + //set position matrix + //the matrix needs to be set at the end behind autogrow and such position influencing properties + ::basegfx::B2DHomMatrix aM; + aM.rotate( -m_fRotationAngleDegree*F_PI/180.0 );//#i78696#->#i80521# + aM.translate( m_nXPos, m_nYPos); + xShapeProp->setPropertyValue( "Transformation", uno::Any( B2DHomMatrixToHomogenMatrix3(aM) ) ); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } +} + +void VTitle::createShapes( + const awt::Point& rPos + , const awt::Size& rReferenceSize + , const awt::Size& rTextMaxWidth ) +{ + if(!m_xTitle.is()) + return; + + uno::Sequence< uno::Reference< XFormattedString > > aStringList = m_xTitle->getText(); + if(!aStringList.hasElements()) + return; + + m_nXPos = rPos.X; + m_nYPos = rPos.Y; + + uno::Reference< beans::XPropertySet > xTitleProperties( m_xTitle, uno::UNO_QUERY ); + + try + { + double fAngleDegree = 0; + xTitleProperties->getPropertyValue( "TextRotation" ) >>= fAngleDegree; + m_fRotationAngleDegree += fAngleDegree; + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("chart2", "" ); + } + + sal_Int32 nTextMaxWidth; + if (m_fRotationAngleDegree <= 15.0 || m_fRotationAngleDegree >= 345.0 + || (m_fRotationAngleDegree >= 165.0 && m_fRotationAngleDegree <= 195.0)) + nTextMaxWidth = rTextMaxWidth.Width; + else + nTextMaxWidth = rTextMaxWidth.Height; + + ShapeFactory* pShapeFactory = ShapeFactory::getOrCreateShapeFactory(m_xShapeFactory); + m_xShape =pShapeFactory->createText( m_xTarget, rReferenceSize, rPos, aStringList, xTitleProperties, + m_fRotationAngleDegree, m_aCID, nTextMaxWidth ); +} + +} //namespace chart + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/chart2/source/view/main/VTitle.hxx b/chart2/source/view/main/VTitle.hxx new file mode 100644 index 000000000..261458025 --- /dev/null +++ b/chart2/source/view/main/VTitle.hxx @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#ifndef INCLUDED_CHART2_SOURCE_VIEW_MAIN_VTITLE_HXX +#define INCLUDED_CHART2_SOURCE_VIEW_MAIN_VTITLE_HXX + +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/uno/Reference.h> +#include <rtl/ustring.hxx> +#include <sal/types.h> + +namespace com::sun::star::awt { struct Point; } +namespace com::sun::star::chart2 { class XTitle; } +namespace com::sun::star::drawing { class XShape; } +namespace com::sun::star::drawing { class XShapes; } +namespace com::sun::star::lang { class XMultiServiceFactory; } + +namespace chart +{ + +class VTitle final +{ +public: + explicit VTitle( const css::uno::Reference< css::chart2::XTitle > & xTitle ); + ~VTitle(); + + void init( const css::uno::Reference< css::drawing::XShapes >& xTargetPage + , const css::uno::Reference< css::lang::XMultiServiceFactory >& xFactory + , const OUString& rCID ); + + void createShapes( const css::awt::Point& rPos + , const css::awt::Size& rReferenceSize + , const css::awt::Size& nTextMaxWidth ); + + double getRotationAnglePi() const; + css::awt::Size getUnrotatedSize() const; + css::awt::Size getFinalSize() const; + void changePosition( const css::awt::Point& rPos ); + +private: + css::uno::Reference< css::drawing::XShapes > m_xTarget; + css::uno::Reference< css::lang::XMultiServiceFactory> m_xShapeFactory; + css::uno::Reference< css::chart2::XTitle > m_xTitle; + css::uno::Reference< css::drawing::XShape > m_xShape; + OUString m_aCID; + + double m_fRotationAngleDegree; + sal_Int32 m_nXPos; + sal_Int32 m_nYPos; +}; + +} //namespace chart +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |