// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef CHART_WCARTESIAN_CHART_H_
#define CHART_WCARTESIAN_CHART_H_

#include <Wt/Chart/WAbstractChart>
#include <Wt/Chart/WAxis>
#include <Wt/Chart/WDataSeries>
#include <Wt/WPaintDevice>
#include <Wt/WContainerWidget>

namespace Wt {

class WAbstractItemModel;
class WPainter;
class WPainterPath;

  namespace Chart {

class WChart2DRenderer;
class WChartPalette;

/*! \class WCartesianChart Wt/Chart/WCartesianChart Wt/Chart/WCartesianChart
 *  \brief A cartesian chart.
 *
 * A cartesian chart is a chart that uses X and Y axes. It can display
 * one or multiple data series, which each may be rendered using bars,
 * lines, areas, or points.
 *
 * To use a cartesian chart, the minimum you need to do is set a model
 * using setModel(), set the model column that holds the X data using
 * setXSeriesColumn(int modelColumn), and add one or more series using
 * addSeries(const WDataSeries&). Each series corresponds to one data
 * column that holds Y data.
 *
 * A cartesian chart is either a \link Chart::CategoryChart
 * CategoryChart\endlink or a \link Chart::ScatterPlot
 * ScatterPlot\endlink.
 *
 * In a <b>CategoryChart</b>, the X series represent different
 * categories, which are listed consecutively in model row order. The
 * X axis scale is set to \link Chart::CategoryScale
 * CategoryScale\endlink.
 *
 * \image html ChartWCartesianChart-1.png "A category chart with bar series"
 *
 * Each series may be rendered differently, and this is configured in
 * the data series (see WDataSeries for more information).
 *
 * In a <b>ScatterPlot</b>, the X series data are interpreted as
 * numbers on a numerical scale. The scale for the X axis defaults to
 * a \link Chart::LinearScale LinearScale\endlink, but this may be
 * changed to a \link Chart::DateScale DateScale\endlink when the X
 * series contains dates (of type WDate) to create a time series
 * chart, or to a \link Chart::LogScale LogScale\endlink. A
 * ScatterPlot supports the same types of data series as a
 * CategoryChart, but does not support stacking. In a scatter plot,
 * the X series do not need to be ordered in increasing values, and
 * may be set differently for each dataseries using
 * WDataSeries::setXSeriesColumn(int modelColumn).
 *
 * \image html ChartWCartesianChart-2.png "A time series scatter plot with line series"
 *
 * Missing data in a model series Y values is interpreted as a
 * <i>break</i>. For curve-like series, this breaks the curve (or
 * line).
 *
 * The cartesian chart has support for dual Y axes. Each data series may
 * be bound to one of the two Y axes. By default, only the first Y axis
 * is displayed. To show the second Y axis you will need to call:
 * 
 * \if cpp
 * \code
 * chart->axis(Y2Axis).setVisible(true);
 * \endcode
 * \endif
 *
 * By default a chart has a horizontal X axis and a vertical Y axis,
 * which corresponds to a \link Wt::Vertical Vertical\endlink
 * orientation. The orientation may be changed to \link Wt::Horizontal
 * Horizontal\endlink using setOrientation().
 *
 * The styling of the series data are dictated by a palette which may
 * be set using setPalette(WChartPalette *), but may be overridden by
 * settings in each data series.
 *
 * <h3>CSS</h3>
 *
 * Styling through CSS is not applicable.
 *
 * \sa WDataSeries, WAxis
 * \sa WPieChart
 *
 * \ingroup charts modelview
 */
class WT_API WCartesianChart : public WAbstractChart
{
public:
  /*! \brief Creates a new cartesian chart.
   *
   * Creates a cartesian chart of type \link Wt::Chart::CategoryChart
   * CategoryChart\endlink.
   */
  WCartesianChart(WContainerWidget *parent = 0);

  /*! \brief Creates a new cartesian chart.
   *
   * Creates a cartesian chart of the indicated \p type.
   */
  WCartesianChart(ChartType type, WContainerWidget *parent = 0);

  /*! \brief Sets the chart type.
   *
   * The chart type determines how (x,y) data are interpreted. In a
   * \link Wt::Chart::CategoryChart CategoryChart\endlink, the X
   * values are categories, and these are plotted consecutively,
   * evenly spaced, and in row order. In a \link
   * Wt::Chart::ScatterPlot ScatterPlot\endlink, the X values are
   * interpreted numerically (as for Y values).
   *
   * The default chart type is a \link Wt::Chart::CategoryChart
   * CategoryChart\endlink.
   *
   * \sa type()
   * \sa WAxis::setScale(), axis(Axis)
   */
  void setType(ChartType type);

  /*! \brief Returns the chart type.
   *
   * \sa setType()
   */
  ChartType type() const { return type_; }

  /*! \brief Sets the chart orientation.
   *
   * Sets the chart orientation, which corresponds to the orientation of
   * the Y axis: a Wt::Vertical orientation corresponds to the conventional
   * way of a horizontal X axis and vertical Y axis. A Wt::Horizontal
   * orientation is the other way around.
   *
   * The default orientation is Wt::Vertical.
   *
   * \sa orientation()
   */
  void setOrientation(Orientation orientation);

  /*! \brief Returns the chart orientation.
   *
   * \sa setOrientation()
   */  
  Orientation orientation() const { return orientation_; }

  /*! \brief Sets the the model column for the X series.
   *
   * Use this method to specify the default data for the X series.
   * For a \link Wt::Chart::ScatterPlot ScatterPlot\endlink this is
   * mandatory if an X series is not specified for every
   * \link Wt::Chart::WDataSeries WDataSeries\endlink. For a
   * \link Wt::Chart::CategoryChart CategoryChart\endlink, if not
   * specified, an increasing series of integer numbers will be
   * used (1, 2, ...).
   *
   * Scatterplot dataseries may each individually be given its own
   * X series data using WDataSeries::setXSeriesColumn(int modelColumn)
   *
   * The default value is -1 (not specified).
   *
   * The series column is reset to -1 when the model is set (or
   * changed). Thus you need to set a model before configuring the
   * series.
   *
   * \sa XSeriesColumn()
   */
  void setXSeriesColumn(int modelColumn);

  /*! \brief Returns the model column for the X series.
   *
   * \sa setXSeriesColumn()
   */
  int XSeriesColumn() const { return XSeriesColumn_; }

  //later, activates a 3D plot
  //void setXYData(int modelColumnX, int modelColumnY);
  //bool is3D() const;

  /*! \brief Adds a data series.
   *
   * A single chart may display one or more data series. Each data series
   * displays data from a single model column in the chart. Series are
   * plotted in the order that they have been added to the chart.
   *
   * The series column is reset to -1 when the model is set (or
   * changed). Thus you need to set a model before configuring the
   * series.
   *
   * \sa removeSeries(), setSeries()
   */
  void addSeries(const WDataSeries& series);

  /*! \brief Removes a data series.
   *
   * This removes the first data series which plots the given
   * \p modelColumn.
   *
   * \sa addSeries(), setSeries()
   */
  void removeSeries(int modelColumn);

  /*! \brief Sets all data series.
   *
   * Replaces the current list of series with the new list.
   *
   * \sa series(), addSeries(), removeSeries()
   */
  void setSeries(const std::vector<WDataSeries>& series);

  /*! \brief Returns a data series corresponding to a data column.
   *
   * Returns a reference to the first data series that plots data
   * from \p modelColumn.
   */
  WDataSeries& series(int modelColumn);

  /*! \brief Returns a data series corresponding to a data column.
   *
   * Returns a const reference to the first data series that plots data
   * from \p modelColumn.
   */
  const WDataSeries& series(int modelColumn) const;

  /*! \brief Returns a list with the current data series.
   *
   * Returns the complete list of current data series.
   *
   * \sa setSeries(const std::vector<WDataSeries>&)
   */
  const std::vector<WDataSeries>& series() const { return series_; }

  /*! \brief Returns a chart axis.
   *
   * Returns a reference to the specified \p axis.
   */
  WAxis& axis(Axis axis);

  /*! \brief Accesses a chart axis.
   *
   * Returns a const reference to the specified \p axis.
   */
  const WAxis& axis(Axis axis) const;

  /*! \brief Sets the margin between bars of different series.
   *
   * Use this method to change the margin that is set between bars of
   * different series. The margin is specified as a fraction of the
   * width. For example, a value of 0.1 adds a 10% margin between bars
   * of each series. Negative values are also allowed. For example, use
   * a margin of -1 to plot the bars of different series on top of
   * each other.
   *
   * The default value is 0.
   */
  void setBarMargin(double margin);

  /*! \brief Returns the margin between bars of different series.
   *
   * \sa setBarMargin(double)
   */
  double barMargin() const { return barMargin_; }

  /*! \brief Enables the legend.
   *
   * The location of the legend can be configured using
   * setLegendLocation(). Only series for which the legend is enabled
   * or included in this legend.
   *
   * The default value is \c false.
   *
   * \sa see WDataSeries::isLegendEnabled(), setLegendLocation()
   */
  void setLegendEnabled(bool enabled);

  /*! \brief Returns whether the legend is enabled.
   *
   * \sa setLegendEnabled()
   */
  bool isLegendEnabled() const { return legend_; }

  /*! \brief Configures the legend location.
   *
   * The legend can be renderd either inside or outside of the chart
   * area. When \p location is \link Wt::Chart::LegendInside
   * Chart::LegendInside\endlink, the legend will be rendered inside
   * the chart. When \p location is \link Wt::Chart::LegendOutside
   * Chart::Legendoutside\endlink, the legend is rendered outside the
   * chart, in the chart padding area.
   *
   * The provided \p side can either be Wt::Left, Wt::Right, Wt::Top,
   * Wt::Bottom and configures the side of the chart at which the
   * legend is displayed.
   *
   * The \p alignment specifies how the legend is aligned. This can be
   * a horizontal alignment flag (Wt::AlignLeft, Wt::AlignCenter, or
   * Wt::AlignRight), when the \p side is Bottom or Top, or a vertical
   * alignment flag (Wt::AlignTop, Wt::AlignMiddle, or Wt::AlignBottom)
   * when the \p side is Left or Right.
   *
   * The default location is \link Wt::Chart::LegendOutside
   * Chart::LegendOutside\endlink, Wt::Right and Wt::AlignMiddle.
   *
   * To have more control over the legend, you could reimplement the
   * renderLegendItem() method to customize how one item in the legend
   * is rendered, or, alternatively you can disable the legend
   * generated by the chart itself, and reimplement the paint() method
   * in which you use the renderLegendItem() method repeatedly to
   * render a customized legend.
   *
   * \sa WDataSeries::setLegendEnabled()
   */
  void setLegendLocation(LegendLocation location, Side side,
			 AlignmentFlag alignment);

  /*! \brief Configures the legend decoration.
   *
   * This configures the font, border and background for the legend.
   *
   * The default font is a 10pt sans serif font (the same as the
   * default axis label font), the default \p border is \link
   * Wt::NoPen NoPen\endlink and the default \p background is \link
   * Wt::NoBrush NoBrush\endlink.
   *
   * \sa setLegendEnabled()
   */
  void setLegendStyle(const WFont& font, const WPen& border,
		      const WBrush& background);

  /*! \brief Returns the legend location.
   *
   * \sa setLegendLocation()
   */
  LegendLocation legendLocation() const { return legendLocation_; }

  /*! \brief Returns the legend side.
   *
   * \sa setLegendLocation()
   */
  Side legendSide() const { return legendSide_; }

  /*! \brief Returns the legend alignment.
   *
   * \sa setLegendLocation()
   */
  AlignmentFlag legendAlignment() const { return legendAlignment_; }

  /*! \brief Returns the legend columns.
   *
   * \sa setLegendColumns()
   */
  int legendColumns() const { return legendColumns_; }

  /*! \brief Returns the legend column width.
   *
   * \sa setLegendColumns()
   */
  WLength legendColumnWidth() const { return legendColumnWidth_; }

  /*! \brief Returns the legend font.
   *
   * \sa setLegendStyle()
   */
  WFont legendFont() const { return legendFont_; }

  /*! \brief Returns the legend border pen.
   *
   * \sa setLegendStyle()
   */
  WPen legendBorder() const { return legendBorder_; }

  /*! \brief Returns the legend background brush.
   *
   * \sa setLegendStyle()
   */
  WBrush legendBackground() const { return legendBackground_; }

  /*! \brief Configures multiple legend columns.
   *
   * Multiple columns are typically useful when placing the legend at
   * the top or at the bottom of the chart.
   *
   * The default value is a single column, 100 pixels wide. 
   */
  void setLegendColumns(int columns, const WLength& width);
  
  virtual void paint(WPainter& painter, const WRectF& rectangle = WRectF())
    const;

  /*! \brief Draws the marker for a given data series.
   *
   * Draws the marker for the indicated \p series in the \p result.
   * This method is called while painting the chart, and you may
   * want to reimplement this method if you wish to provide a custom
   * marker for a particular data series.
   *
   * \sa setLegendEnabled()
   */
  virtual void drawMarker(const WDataSeries& series, WPainterPath& result)
    const;

  /*! \brief Renders the legend icon for a given data series.
   *
   * Renders the legend icon for the indicated \p series in the
   * \p painter at position \p pos.
   *
   * This method is called while rendering a legend item, and you may
   * want to reimplement this method if you wish to provide a custom
   * legend icon for a particular data series.
   *
   * \sa renderLegendItem()
   */
  virtual void renderLegendIcon(WPainter& painter, const WPointF& pos,
				const WDataSeries& series) const;

  /*! \brief Renders the legend item for a given data series.
   *
   * Renders the legend item for the indicated \p series in the
   * \p painter at position \p pos. The default implementation
   * draws the marker, and the series description to the right. The
   * series description is taken from the model's header data for that
   * series' data column.
   *
   * This method is called while painting the chart, and you may
   * want to reimplement this method if you wish to provide a custom
   * marker for a particular data series.
   *
   * \sa setLegendEnabled()
   */
  virtual void renderLegendItem(WPainter& painter, const WPointF& pos,
				const WDataSeries& series) const;

  /*! \brief Maps from device coordinates to model coordinates.
   *
   * Maps a position in the chart back to model coordinates.
   *
   * This uses the axis dimensions that are based on the latest chart
   * rendering. If you have not yet rendered the chart, or wish to already
   * the mapping reflect model changes since the last rendering, you should
   * call initLayout() first.
   *
   * \sa mapToDevice()
   */
  WPointF mapFromDevice(const WPointF& point,
			Axis ordinateAxis = OrdinateAxis) const;

  /*! \brief Maps model values onto chart coordinates.
   *
   * This returns the chart device coordinates for a (x,y) pair of model
   * values.
   *
   * This uses the axis dimensions that are based on the latest chart
   * rendering. If you have not yet rendered the chart, or wish to already
   * the mapping reflect model changes since the last rendering, you should
   * call initLayout() first.
   *
   * The \p xSegment and \p ySegment arguments are relevant only when
   * the corresponding axis is broken using WAxis::setBreak(). Then,
   * its possible values may be 0 (below the break) or 1 (above the
   * break).
   *
   * \sa mapFromDevice()
   */
  WPointF mapToDevice(const boost::any& xValue, const boost::any& yValue,
		      Axis axis = OrdinateAxis, int xSegment = 0,
		      int ySegment = 0) const;

  /*! \brief Initializes the chart layout.
   *
   * A cartesian chart delegates the rendering and layout of the chart
   * and its axes to a WChart2DRenderer. As a consequence, the mapping
   * between model and device coordinates is also established by this
   * class, which is only created on-demand when painging.
   *
   * If you wish to establish the layout, in order to use the
   * mapFromDevice() and mapToDevice() methods before the chart has
   * been rendered, you should call this method.
   *
   * Unless a specific chart rectangle is specified, the entire widget
   * area is assumed.
   */
  void initLayout(const WRectF& rectangle = WRectF());

  /*! \brief Creates a widget which renders the a legend item.
   * 
   * The legend item widget will contain a text and a WPaintedWidget 
   * which draws the series' symbol.
   */
  WWidget* createLegendItemWidget(int index);

  /*! \brief Adds a data point area (used for displaying e.g. tooltips).
   *
   * You may want to specialize this is if you wish to modify (or
   * delete) the area.
   *
   * \note Currently, an area is only created if the Wt::ToolTipRole data at the
   *       data point is not empty.
   */
  virtual void addDataPointArea(const WDataSeries& series,
				const WModelIndex& xIndex,
				WAbstractArea *area);
  /*! \brief Sets the padding between the chart area and the axes.
   *
   * \sa axisPadding()
   */
  void setAxisPadding(int axisPadding);

  /*! \brief Returns the padding between the chart area and the axes.
   *
   * \sa setAxisPadding()
   */
  int axisPadding() const { return axisPadding_; }

protected:
  void paintEvent(WPaintDevice *paintDevice);

  /*! \brief Creates a renderer which renders the chart.
   *
   * The rendering of the chart is delegated to a WChart2DRenderer
   * class, which will render the chart within the \p rectangle of
   * the \p painter.
   *
   * You may want to reimplement this method if you wish to override
   * one or more aspects of the rendering, by returning an new instance
   * of a specialized WChart2DRenderer class.
   *
   * After rendering, the renderer is deleted.
   *
   * \sa WChart2DRenderer::render()
   */
  virtual WChart2DRenderer *createRenderer(WPainter& painter,
					   const WRectF& rectangle) const;

private:
  Orientation               orientation_;
  int                       XSeriesColumn_;
  ChartType                 type_;
  std::vector<WDataSeries>  series_;
  WAxis                     axes_[3];
  double                    barMargin_;
  bool                      legend_;
  LegendLocation            legendLocation_;
  Side                      legendSide_;
  AlignmentFlag             legendAlignment_;
  int                       legendColumns_;
  WLength                   legendColumnWidth_;
  WFont                     legendFont_;
  WPen                      legendBorder_;
  WBrush                    legendBackground_;
  int                       axisPadding_;

  void init();
  virtual void modelColumnsInserted(const WModelIndex& parent,
				    int start, int end);
  virtual void modelColumnsRemoved(const WModelIndex& parent,
				   int start, int end);
  virtual void modelRowsInserted(const WModelIndex& parent,
				 int start, int end);
  virtual void modelRowsRemoved(const WModelIndex& parent,
				int start, int end);
  virtual void modelDataChanged(const WModelIndex& topLeft,
			        const WModelIndex& bottomRight);

  virtual void modelChanged();
  virtual void modelReset();

  int seriesIndexOf(int modelColumn) const;

  WPointF hv(double x, double y, double width) const;
  WPointF inverseHv(double x, double y, double width) const;

  class IconWidget : public WPaintedWidget {
  public:
    IconWidget(WCartesianChart *chart, 
	       int index, 
	       WContainerWidget *parent = 0);
    
  protected:
    virtual void paintEvent(WPaintDevice *paintDevice);
    
  private:
    WCartesianChart* chart_;
    int index_;
  };

  friend class WDataSeries;
  friend class WChart2DRenderer;
};

  }
}

#endif // CHART_WCARTESIAN_CHART_H_
