View Issue Details

IDProjectCategoryView StatusLast Update
0006316JEDI VCL03 Donationspublic2015-09-21 17:47
ReportervintagedaveAssigned Toobones 
PrioritynormalSeverityfeatureReproducibilityalways
Status resolvedResolutionfixed 
Product Version3.47 
Target VersionFixed in Version3.49 
Summary0006316: Add area graphs to TJvChart
DescriptionAn area graph is a line graph with the area under the line filled in (like this: http://www.qualia.hr/wp-content/uploads/2011/11/area.jpg )

I have written a small patch to add this to TJvChart, filling the area under the line with a color between the line color and page color.

The main problem with this kind of chart is when lines overlap - does the background hide the underlying graph? In this implementation I draw the backgrounds first and lines second, so even if a line dips behind another line, it will still be visible. (More advanced would be to alpha-blend the filled areas, but...)

Rather than adding another chart type (eg area chart) it is an option "FillUnderLine" property that's used when drawing line charts, and is off by default.

I also (I know I should put this as another issue, sorry) made a tweak so when the axis width is 0, it won't draw (otherwise it draws a 1px line) and ditto for the rectangle around the whole graph. This is to give a more modern, minimal look without lines everywhere.
Additional InformationI have attached a patch against the latest downloadable version of the JVCL.
TagsNo tags attached.

Activities

2014-08-19 18:28

 

jvchart fill patch.patch (9,317 bytes)
--- C:/projects/Components/JVCL/jvcl/run/JvChart.pas	Tue May  6 11:48:52 2014
+++ C:/projects/Components/JVCL/jvcl/run/JvChart - DM line fill 2.pas	Tue Aug 19 19:05:42 2014
@@ -19,6 +19,7 @@
 Contributor(s):
     Warren Postma (warrenpstma att hotmail dott com)
     M�rten Henrichson/AABSoft (no email known)
+    David Millington (vintagedave att gmail dott com)
 
     Contains some code which is
         (C) 1996-1998 AABsoft and M�rten Henrichson
@@ -83,6 +84,7 @@
                         - Calls only JclMath.IsNaN, not Math.IsNaN, which doesn't
                         exist on older Delphi/BCB versions.
                        - Added CopyFloatingMarkers (thought I did that yesterday but missed it)
+  2014-08-19 - DM - Added area graph option (ie, filling under the line of a line graph)
 
 You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
 located at http://jvcl.delphi-jedi.org
@@ -470,6 +472,7 @@
     FXAxisDivisionMarkers: Boolean; // Do you want grid-paper look?
     FXAxisHeader: string;
     FMarkerSize:Integer;
+    FFillUnderLine : Boolean; // New 2014
     FXLegends: TStringList; // Text labels.
     FXLegendMaxTextWidth: Integer; // runtime: display width (pixels) of widest string in FXLegends[1:X].
     FXAxisValuesPerDivision: Integer;
@@ -603,6 +606,7 @@
     { Y Range }
     { plotting markers }
     property MarkerSize: Integer read FMarkerSize write FMarkerSize default JvChartDefaultMarkerSize;
+    property FillUnderLine : Boolean read FFillUnderLine write FFillUnderLine default False;
     { !! New: Primary (left side) Y axis, and Secondary (right side) Y Axis !!}
     property PrimaryYAxis: TJvChartYAxisOptions read FPrimaryYAxis write SetPrimaryYAxis;
     property SecondaryYAxis: TJvChartYAxisOptions read FSecondaryYAxis write SetSecondaryYAxis;
@@ -1827,6 +1831,7 @@
   FPenColors[11] := clAqua;
 
   FChartKind := ckChartLine;
+  FFillUnderLine := false;
 
   FPenCount := 1;
 
@@ -2730,18 +2735,20 @@
   ACanvas.Brush.Style := bsClear;
 
   { NEW: Box around entire chart area. }
-  X1 := Round(XOrigin);
-  X2 := Round(Options.XStartOffset + Options.XPixelGap * VC);
-  Y1 := Options.YStartOffset - 1;
-  Y2 := Round(YOrigin) + 1; // was YTempOrigin
+  if Options.AxisLineWidth <> 0 then begin
+    X1 := Round(XOrigin);
+    X2 := Round(Options.XStartOffset + Options.XPixelGap * VC);
+    Y1 := Options.YStartOffset - 1;
+    Y2 := Round(YOrigin) + 1; // was YTempOrigin
 
-  if Y2 > Height then
-  begin
-    // I suspect that the value of YPixelGap is too large in some cases.
-    Options.PrimaryYAxis.Normalize;
-    //OutputDebugString( PChar('Y2 is bogus. PYVC='+IntToStr(PYVC)) );
+    if Y2 > Height then
+    begin
+      // I suspect that the value of YPixelGap is too large in some cases.
+      Options.PrimaryYAxis.Normalize;
+      //OutputDebugString( PChar('Y2 is bogus. PYVC='+IntToStr(PYVC)) );
+    end;
+    MyRectangle(ACanvas, X1, Y1, X2, Y2);
   end;
-  MyRectangle(ACanvas, X1, Y1, X2, Y2);
 
   ACanvas.Brush.Style := bsSolid;
 end;
@@ -2752,6 +2759,8 @@
 var
   ACanvas: TCanvas;
 begin
+  if Options.AxisLineWidth = 0 then Exit;
+
   ACanvas := GetChartCanvas(false);
   ACanvas.Pen.Style := psSolid;
   ACanvas.Pen.Color := Options.AxisLineColor;
@@ -2767,8 +2776,9 @@
 var
   LCanvas: TCanvas;
 begin
+  if Options.AxisLineWidth = 0 then Exit;
+
   LCanvas := GetChartCanvas(false);
-
   LCanvas.Pen.Style := psSolid;
   LCanvas.Pen.Color := Options.AxisLineColor;
   LCanvas.Pen.Width := Options.AxisLineWidth; // was missing. Added Feb 2005. -WPostma.
@@ -3310,7 +3320,6 @@
   end;
 
   // Keep Y in visible chart range:
-
   function GraphConstrainedLineY(Pen, Sample: Integer): Double;
   var
     V: Double;
@@ -3336,6 +3345,26 @@
       Result := Options.YStartOffset - 2; // Not quite good enough, but better than before.
   end;
 
+  function GetUnderLineFillColor(const A, B: TColor) : TColor;
+  const
+    Lerp = 0.85; // 0-1, where 0 is fully A and 1 is fully B. This value seems good
+  var
+    AR, AG, AB, BR, BG, BB: Byte;
+  begin
+    AR := GetRValue(A);
+    AG := GetGValue(A);
+    AB := GetBValue(A);
+    BR := GetRValue(B);
+    BG := GetGValue(B);
+    BB := GetBValue(B);
+
+    Result := RGB(
+      Round(AR + Lerp * (BR - AR)),
+      Round(AG + Lerp * (BG - AG)),
+      Round(AB + Lerp * (BB - AB))
+    );
+  end;
+
   procedure PlotGraphChartLine;
   var
     I, I2, J, Y1: Integer;
@@ -3359,6 +3388,7 @@
       // No line types?
       if Options.PenStyle[I] = psClear then
         Continue;
+
       SetLineColor(ACanvas, I);
       J := 0;
       V := GraphConstrainedLineY(I, J);
@@ -3406,8 +3436,9 @@
                 end;
               end;
             end;
+            //OldV := V; // keep track of last valid value, for handling gaps.
+
             MyPenLineTo(ACanvas, Round(XOrigin + J * LineXPixelGap), Y);
-            //OldV := V; // keep track of last valid value, for handling gaps.
           end;
         end;
       end;
@@ -3417,6 +3448,103 @@
     SetLineColor(ACanvas, jvChartAxisColorIndex);
   end;
 
+  procedure PlotGraphChartLineFill;
+  var
+    I, I2, J, Y1: Integer;
+    V, LineXPixelGap: Double;
+    NanFlag: Boolean;
+    VC: Integer;
+    FillPoints : array of TPoint;
+    FillPolyIndex : Integer;
+  begin
+    Assert(Assigned(ACanvas));
+    Assert(Assigned(ACanvas.Brush));
+
+    SetLength(FillPoints, 0); // Not set to 0 by compiler
+    try
+      VC := Options.XValueCount;
+      if VC < 2 then
+        VC := 2;
+      LineXPixelGap := ((Options.XEnd - 2) - Options.XStartOffset) / (VC - 1);
+
+      for I := 0 to Options.PenCount - 1 do
+      begin
+        // No line types?
+        if Options.PenStyle[I] = psClear then
+          Continue;
+
+        SetLength(FillPoints, FData.ValueCount + 2 {start and end points});
+        FillPolyIndex := 0;
+
+        J := 0;
+        V := GraphConstrainedLineY(I, J);
+        NanFlag := IsNaN(V);
+        if not NanFlag then
+        begin
+          Y := Round(V);
+          FillPoints[FillPolyIndex] := Point(Round(XOrigin)+1, Round(YOrigin)); // start at origin
+          Inc(FillPolyIndex);
+          FillPoints[FillPolyIndex] := Point(Round(XOrigin)+1, Y); // add first point
+          Inc(FillPolyIndex);
+        end;
+
+        for J := 1 to Options.XValueCount - 1 do
+        begin
+          V := GraphConstrainedLineY(I, J);
+          if IsNaN(V) then
+          begin
+            NanFlag := True; // skip.
+            FillPoints[FillPolyIndex] := Point(Round(XOrigin + J * LineXPixelGap), Round(YOrigin)); // !!! DEBUG
+            Inc(FillPolyIndex);
+          end
+          else
+          begin
+            if NanFlag then
+            begin // resume, valid value.
+              NanFlag := False;
+              Y := Round(V);
+              // pick up the pen and slide forward
+              FillPoints[FillPolyIndex] := Point(Round(XOrigin + J * LineXPixelGap), Y);
+              Inc(FillPolyIndex);
+            end
+            else
+            begin
+              Y := Round(V);
+              if I > 0 then
+              begin
+                for I2 := 0 to I - 1 do
+                begin
+                  V := GraphConstrainedLineY(I2, J);
+                  if IsNaN(V) then
+                    Continue;
+                end;
+              end;
+
+              FillPoints[FillPolyIndex] := Point(Round(XOrigin + J * LineXPixelGap), Y); // add next point
+              Inc(FillPolyIndex);
+            end;
+          end;
+        end;
+
+        // Add a final point which is the same as the last, but at the bottom
+        if FillPolyIndex > 0 then begin
+          FillPoints[FillPolyIndex] := Point(FillPoints[FillPolyIndex-1].X, Round(YOrigin));
+          Inc(FillPolyIndex);
+          SetLength(FillPoints, FillPolyIndex); // Trim to the actual usage
+
+          // First draw the fill
+          ACanvas.Pen.Style := psClear;
+          ACanvas.Brush.Color := GetUnderLineFillColor(Options.PenColor[I],
+            Options.PenColor[jvChartPaperColorIndex]);
+          ACanvas.Brush.Style := bsSolid;
+          ACanvas.Polygon(FillPoints);
+        end;
+      end;
+    finally
+      SetLength(FillPoints, 0);
+    end;
+  end;
+
   procedure PlotGraphStackedBarAverage;
   var
     I, J: Integer;
@@ -3621,8 +3749,11 @@
       PlotGraphBar;
     ckChartStackedBar:
       PlotGraphStackedBar;
-    ckChartLine: //, ckChartLineWithMarkers:
-      PlotGraphChartLine;
+    ckChartLine: begin//, ckChartLineWithMarkers:
+        if Options.FillUnderLine then
+          PlotGraphChartLineFill; // Do this before drawing lines, so lines are always visible
+        PlotGraphChartLine;
+      end;
     ckChartMarkers:
       PlotGraphChartMarkers;
     ckChartStackedBarAverage:
@@ -5223,6 +5354,8 @@
 
 procedure TJvChart.MyAxisLineTo(ACanvas: TCanvas; X, Y: Integer);
 begin
+  if Options.AxisLineWidth = 0 then Exit;
+
   ACanvas.Pen.Width := Options.AxisLineWidth;
   ACanvas.LineTo(X, Y);
   ACanvas.Pen.Width := 1;
jvchart fill patch.patch (9,317 bytes)

obones

2014-09-03 11:42

administrator   ~0021039

Please provide the zipped sources of a sample application showing this

2014-09-08 15:46

 

JvChart filled demo.zip (27,332 bytes)

2014-09-08 15:47

 

vintagedave

2014-09-08 15:47

reporter   ~0021057

Done. I've attached a sample project and a screenshot of what it looks like.

vintagedave

2014-11-13 13:54

reporter   ~0021071

Any feedback on this? It's been in status "feedback" for two / three months. I added the demo app and screenshot as requested, but I haven't got permissions to change the status to indicate something was done.

obones

2014-12-04 16:21

administrator   ~0021090

This is now in GIT

Issue History

Date Modified Username Field Change
2014-08-19 18:28 vintagedave New Issue
2014-08-19 18:28 vintagedave File Added: jvchart fill patch.patch
2014-09-03 11:42 obones Note Added: 0021039
2014-09-03 11:42 obones Status new => feedback
2014-09-08 15:46 vintagedave File Added: JvChart filled demo.zip
2014-09-08 15:47 vintagedave File Added: JvChart filled demo - screenshot.png
2014-09-08 15:47 vintagedave Note Added: 0021057
2014-11-13 13:54 vintagedave Note Added: 0021071
2014-12-04 15:04 obones Status feedback => acknowledged
2014-12-04 16:21 obones Note Added: 0021090
2014-12-04 16:21 obones Status acknowledged => resolved
2014-12-04 16:21 obones Fixed in Version => Daily / GIT
2014-12-04 16:21 obones Resolution open => fixed
2014-12-04 16:21 obones Assigned To => obones
2015-09-21 17:47 obones Fixed in Version Daily / GIT => 3.49