mirror of
https://github.com/Rayerd/dfl.git
synced 2025-04-26 04:59:55 +03:00
Add dfl.chart.LineGraphRenderer
Update TableRenderer
This commit is contained in:
parent
d2e832ec54
commit
262711208a
13 changed files with 829 additions and 36 deletions
|
@ -5,10 +5,10 @@ DFL is a Win32 windowing library for the D language.
|
|||
|
||||
## Recent major features
|
||||
- **Module "dfl.chart" is now comming.**
|
||||
- **TableRenderer (with example)**
|
||||
- ~LineGraphRenderer~
|
||||
- TableRenderer (with example)
|
||||
- **LineGraphRenderer (with example)**
|
||||
- ~TimeChartRenderer~
|
||||
- **Add simple clock "Dclock" as an example of DFL application.**
|
||||
- Add simple clock "Dclock" as an example of DFL application.
|
||||
- Module "dfl.printing" is now comming.
|
||||
- PrintDialog
|
||||
- PrintSetupDialog
|
||||
|
@ -40,6 +40,7 @@ DFL is a Win32 windowing library for the D language.
|
|||

|
||||

|
||||

|
||||

|
||||
|
||||
## Build and Install (dfl.lib and dfl_debug.lib)
|
||||
### 1. Set environment variables
|
||||
|
|
16
examples/linegraphrenderer/.gitignore
vendored
Normal file
16
examples/linegraphrenderer/.gitignore
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
.dub
|
||||
docs.json
|
||||
__dummy.html
|
||||
docs/
|
||||
/hello_dfl
|
||||
hello_dfl.so
|
||||
hello_dfl.dylib
|
||||
hello_dfl.dll
|
||||
hello_dfl.a
|
||||
hello_dfl.lib
|
||||
hello_dfl-test-*
|
||||
*.exe
|
||||
*.pdb
|
||||
*.o
|
||||
*.obj
|
||||
*.lst
|
13
examples/linegraphrenderer/.vscode/launch.json
vendored
Normal file
13
examples/linegraphrenderer/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "C++ Launch (Windows) linegraphrenderer",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"program": "./bin/linegraphrenderer.exe",
|
||||
"console": "internalConsole"
|
||||
}
|
||||
]
|
||||
}
|
20
examples/linegraphrenderer/.vscode/tasks.json
vendored
Normal file
20
examples/linegraphrenderer/.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "dub",
|
||||
"run": false,
|
||||
"cwd": ".",
|
||||
"compiler": "$current",
|
||||
"archType": "$current",
|
||||
"buildType": "$current",
|
||||
"configuration": "$current",
|
||||
"problemMatcher": [
|
||||
"$dmd"
|
||||
],
|
||||
"group": "build",
|
||||
"label": "dub: Build linegraphrenderer_sample",
|
||||
"detail": "dub build --compiler=dmd.EXE -a=x86_64 -b=debug -c=application"
|
||||
}
|
||||
]
|
||||
}
|
2
examples/linegraphrenderer/README.md
Normal file
2
examples/linegraphrenderer/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Screen Shot
|
||||

|
16
examples/linegraphrenderer/dub.json
Normal file
16
examples/linegraphrenderer/dub.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"authors": ["haru-s"],
|
||||
"copyright": "Copyright (C) 2024 haru-s",
|
||||
"description": "DFL sample code.",
|
||||
"name": "linegraphrenderer",
|
||||
"targetType": "executable",
|
||||
"targetPath": "bin",
|
||||
"dependencies": {
|
||||
"dfl": {
|
||||
"path": "../../../dfl"
|
||||
}
|
||||
},
|
||||
"lflags-windows-x86_omf-dmd": ["/exet:nt/su:windows:6.0"],
|
||||
"lflags-windows-x86_mscoff-dmd": ["/SUBSYSTEM:WINDOWS", "/ENTRY:mainCRTStartup"],
|
||||
"lflags-windows-x86_64-dmd": ["/SUBSYSTEM:WINDOWS", "/ENTRY:mainCRTStartup"]
|
||||
}
|
BIN
examples/linegraphrenderer/image/screenshot.png
Normal file
BIN
examples/linegraphrenderer/image/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 123 KiB |
12
examples/linegraphrenderer/linegraphrenderer.code-workspace
Normal file
12
examples/linegraphrenderer/linegraphrenderer.code-workspace
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"d.projectImportPaths": [
|
||||
"..\\..\\..\\dfl\\source"
|
||||
]
|
||||
}
|
||||
}
|
3
examples/linegraphrenderer/shell.bat
Normal file
3
examples/linegraphrenderer/shell.bat
Normal file
|
@ -0,0 +1,3 @@
|
|||
set dmd_path=c:\d\dmd2\windows
|
||||
set dmc_path=c:\dmc\dm
|
||||
cmd
|
111
examples/linegraphrenderer/source/linegraphrenderer_sample.d
Normal file
111
examples/linegraphrenderer/source/linegraphrenderer_sample.d
Normal file
|
@ -0,0 +1,111 @@
|
|||
import dfl;
|
||||
|
||||
version(Have_dfl) // For DUB.
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
pragma(lib, "dfl.lib");
|
||||
}
|
||||
|
||||
class MainForm : Form
|
||||
{
|
||||
alias CustomLineGraphRenderer = LineGraphRenderer!(string,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int);
|
||||
CustomLineGraphRenderer _graph;
|
||||
|
||||
alias CustomLineGraphRenderer2 = LineGraphRenderer!(int,int,int);
|
||||
CustomLineGraphRenderer2 _graph2;
|
||||
|
||||
// alias CustomTableRenderer = TableRenderer!(string,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int);
|
||||
// CustomTableRenderer _table;
|
||||
|
||||
// alias CustomTableRenderer2 = TableRenderer!(int,int,int);
|
||||
// CustomTableRenderer2 _table2;
|
||||
|
||||
public this()
|
||||
{
|
||||
this.text = "LineGraphRenderer example";
|
||||
this.size = Size(1000, 800);
|
||||
string csv =
|
||||
"教科,山田,佐藤,井上,田中,木下,藤原,山本,大森,伊藤,高橋,鈴木,中村,小林,松井,木村,近藤\n" ~
|
||||
"国語,70,80,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n" ~
|
||||
"算数,60,90,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n" ~
|
||||
"理科,80,70,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n" ~
|
||||
"社会,90,60,80,75,68,65,55,48,45,38,35,25,20,10,5,1\n";
|
||||
_graph = new CustomLineGraphRenderer(csv, 4);
|
||||
_graph.showLegend = true;
|
||||
_graph.legendLineHeight = 18;
|
||||
_graph.chartMargins = ChartMargins(50, 50, 50, 50);
|
||||
_graph.plotPointSize = 10;
|
||||
_graph.verticalZeroPosition = VerticalZeroPosition.BOTTOM;
|
||||
_graph.plotAreaAndLegendSpanX = 50;
|
||||
_graph.plotAreaAndHorizontalScaleSpanY = 10;
|
||||
_graph.plotAreaLeftPadding = 20;
|
||||
_graph.plotAreaRightPadding = 20;
|
||||
_graph.plotAreaHeightOnDisplay = 300;
|
||||
_graph.hasHorizontalScale = true;
|
||||
_graph.horizontalScaleSpan = 100;
|
||||
_graph.horizonScaleLineInnerSide = 0;
|
||||
_graph.horizonScaleLineOuterSide = 5;
|
||||
_graph.horizontalScaleHeight = 12;
|
||||
_graph.hasVerticalScale = true;
|
||||
_graph.verticalMaxScale = 110;
|
||||
_graph.verticalScaleLineOuterSide = 5;
|
||||
_graph.verticalScaleLineInnerSide = 0;
|
||||
_graph.verticalScaleSpan = 20;
|
||||
_graph.verticalScaleWidth = 40;
|
||||
_graph.backColor = Color.white;
|
||||
_graph.plotAreaBoundsColor = Color.black;
|
||||
_graph.plotLineColorPalette[0] = Color.black;
|
||||
_graph.plotPointFormList[4..8] = PlotPointForm.CROSS;
|
||||
_graph.plotPointFormList[8..12] = PlotPointForm.RECTANGLE;
|
||||
_graph.plotPointFormList[12..16] = PlotPointForm.TRIANGLE;
|
||||
_graph.relocate = Point(50, 50); // Relocate origin point based on top-left margins.
|
||||
|
||||
string csv2 =
|
||||
"A,B,C\n" ~
|
||||
"70,80,80\n" ~
|
||||
"60,90,80\n" ~
|
||||
"80,70,80\n" ~
|
||||
"90,60,80\n";
|
||||
_graph2 = new CustomLineGraphRenderer2(csv2, 4);
|
||||
_graph2.chartMargins = ChartMargins(10, 10, 10, 10);
|
||||
_graph2.relocate = Point(600, 50);
|
||||
|
||||
// _table = new CustomTableRenderer(csv, 4);
|
||||
// _table.location = Point(50, 500);
|
||||
// _table.hasHeader = true;
|
||||
// _table.showHeader = true;
|
||||
// _table.headerLine = true;
|
||||
// _table.width[] = 40;
|
||||
|
||||
// _table2 = new CustomTableRenderer2(csv2, 4);
|
||||
// _table2.hasHeader = true;
|
||||
// _table2.showHeader = true;
|
||||
// _table2.headerLine = true;
|
||||
// _table2.width[] = 40;
|
||||
// _table2.location = Point(680, 200);
|
||||
}
|
||||
|
||||
protected override void onPaint(PaintEventArgs e)
|
||||
{
|
||||
if (_graph)
|
||||
_graph.draw(e.graphics);
|
||||
if (_graph2)
|
||||
_graph2.draw(e.graphics);
|
||||
// if (_table)
|
||||
// _table.draw(e.graphics);
|
||||
// if (_table2)
|
||||
// _table2.draw(e.graphics);
|
||||
}
|
||||
}
|
||||
|
||||
static this()
|
||||
{
|
||||
Application.enableVisualStyles();
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
Application.run(new MainForm());
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
@ -10,7 +10,7 @@ else
|
|||
|
||||
class MainForm : Form
|
||||
{
|
||||
alias CustomTableRenderer = TableRenderer!(string, string, string);
|
||||
alias CustomTableRenderer = TableRenderer!(string, int, int);
|
||||
CustomTableRenderer _table;
|
||||
|
||||
public this()
|
||||
|
@ -18,19 +18,17 @@ class MainForm : Form
|
|||
this.text = "TableRenderer example";
|
||||
this.size = Size(450, 450);
|
||||
string csv =
|
||||
"ID,Name,Value\n" ~
|
||||
"1,Kyoto,100\n" ~
|
||||
"2,Osaka,50\n" ~
|
||||
"3,Tokyo,20\n" ~
|
||||
"4,Aomori,10\n";
|
||||
"教科,大森,山田\n" ~
|
||||
"国語,95,98\n" ~
|
||||
"理科,75,80\n" ~
|
||||
"算数,90,78\n" ~
|
||||
"社会,80,76\n";
|
||||
_table = new CustomTableRenderer(csv);
|
||||
_table.height = 40;
|
||||
_table.width[0] = 50;
|
||||
_table.width[1] = 80;
|
||||
_table.width[2] = 150;
|
||||
_table.width[] = 80;
|
||||
_table.paddingX = 10;
|
||||
_table.paddingY = 12;
|
||||
_table.margin = Point(20, 20);
|
||||
_table.location = Point(20, 20);
|
||||
_table.hasHeader = true; // true : 1st line is header.
|
||||
_table.showHeader = true;
|
||||
_table.firstRecord = 0;
|
||||
|
|
|
@ -12,6 +12,7 @@ private import std.csv;
|
|||
private import std.typecons;
|
||||
private import std.conv;
|
||||
private import std.algorithm;
|
||||
private import std.range;
|
||||
|
||||
///
|
||||
class TableRenderer(T...)
|
||||
|
@ -22,7 +23,14 @@ class TableRenderer(T...)
|
|||
enum DEFAULT_PADDING_Y = 5; ///
|
||||
|
||||
///
|
||||
this(string csv)
|
||||
this(string csv, int numRecords)
|
||||
{
|
||||
this(csv);
|
||||
_firstRecord = 0;
|
||||
_lastRecord = numRecords - 1;
|
||||
}
|
||||
/// ditto
|
||||
this(string csv) // deprecated
|
||||
{
|
||||
_csv = csv;
|
||||
_columns = T.length;
|
||||
|
@ -47,10 +55,10 @@ class TableRenderer(T...)
|
|||
g.fillRectangle(new SolidBrush(_backColor), bounds);
|
||||
// Draw top side line.
|
||||
if (_topSideLine)
|
||||
g.drawLine(new Pen(_lineColor), Point(margin.x, margin.y), Point(bounds.right, margin.y));
|
||||
g.drawLine(new Pen(_lineColor), Point(location.x, location.y), Point(bounds.right, location.y));
|
||||
// Draw header line.
|
||||
if (_showHeader && _hasHeader && _headerLine)
|
||||
g.drawLine(new Pen(_lineColor), Point(margin.x, margin.y + height), Point(bounds.right, margin.y + height));
|
||||
g.drawLine(new Pen(_lineColor), Point(location.x, location.y + height), Point(bounds.right, location.y + height));
|
||||
// Draw header.
|
||||
int row; // -row- is line number in CSV.
|
||||
int viewLine; // -viewLine- is line number on display.
|
||||
|
@ -58,10 +66,10 @@ class TableRenderer(T...)
|
|||
{
|
||||
if (_showHeader)
|
||||
{
|
||||
int y = margin.y + viewLine * height + _paddingY;
|
||||
int y = location.y + viewLine * height + _paddingY;
|
||||
foreach (col, value; csvReader!(Tuple!T)(_csv, null).header)
|
||||
{
|
||||
int x = margin.x + sum(_width[0..col]) + _paddingX;
|
||||
int x = location.x + sum(_width[0..col]) + _paddingX;
|
||||
g.drawText(to!string(value), _headerFont, _textColor, Rect(x, y, _width[col] - _paddingX, _height - _paddingY), _headerTextFormat);
|
||||
}
|
||||
row++;
|
||||
|
@ -79,17 +87,17 @@ class TableRenderer(T...)
|
|||
int rows = (_hasHeader?1:0) + lastRecord - firstRecord + 1;
|
||||
if (firstRecord + (_hasHeader?1:0) <= row && row <= rows)
|
||||
{
|
||||
int y = margin.y + viewLine * height + _paddingY;
|
||||
int y = location.y + viewLine * height + _paddingY;
|
||||
foreach (int col, value; record)
|
||||
{
|
||||
int x = margin.x + sum(_width[0..col]) + _paddingX;
|
||||
int x = location.x + sum(_width[0..col]) + _paddingX;
|
||||
g.drawText(to!string(value), _recordFont, _textColor, Rect(x, y, _width[col] - _paddingX, _height - _paddingY), _recordTextFormat);
|
||||
}
|
||||
// Draw horizontal line.
|
||||
if (_horizontalLine && viewLine < lastRecord - firstRecord + (_showHeader?1:0))
|
||||
{
|
||||
int y2 = margin.y + height * (viewLine + 1);
|
||||
g.drawLine(new Pen(_lineColor), Point(margin.x, y2), Point(bounds.right, y2));
|
||||
int y2 = location.y + height * (viewLine + 1);
|
||||
g.drawLine(new Pen(_lineColor), Point(location.x, y2), Point(bounds.right, y2));
|
||||
}
|
||||
row++;
|
||||
viewLine++;
|
||||
|
@ -102,22 +110,22 @@ class TableRenderer(T...)
|
|||
}
|
||||
// Draw left side line.
|
||||
if (_leftSideLine)
|
||||
g.drawLine(new Pen(_lineColor), Point(margin.x, margin.y), Point(margin.x, margin.y + height * viewLine));
|
||||
g.drawLine(new Pen(_lineColor), Point(location.x, location.y), Point(location.x, location.y + height * viewLine));
|
||||
// Draw right side line.
|
||||
if (_rightSideLine)
|
||||
g.drawLine(new Pen(_lineColor), Point(bounds.right, margin.y), Point(bounds.right, margin.y + height * viewLine));
|
||||
g.drawLine(new Pen(_lineColor), Point(bounds.right, location.y), Point(bounds.right, location.y + height * viewLine));
|
||||
// Draw vertical line.
|
||||
if (_verticalLine)
|
||||
{
|
||||
for (int i; i < _columns - 1; i++)
|
||||
{
|
||||
int w = sum(_width[0..i+1]);
|
||||
g.drawLine(new Pen(_lineColor), Point(margin.x + w, margin.y), Point(margin.x + w, margin.y + height * viewLine));
|
||||
g.drawLine(new Pen(_lineColor), Point(location.x + w, location.y), Point(location.x + w, location.y + height * viewLine));
|
||||
}
|
||||
}
|
||||
// Draw bottom side line.
|
||||
if (_bottomSideLine)
|
||||
g.drawLine(new Pen(_lineColor), Point(margin.x, margin.y + height * viewLine), Point(bounds.right, margin.y + height * viewLine));
|
||||
g.drawLine(new Pen(_lineColor), Point(location.x, location.y + height * viewLine), Point(bounds.right, location.y + height * viewLine));
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -180,19 +188,28 @@ class TableRenderer(T...)
|
|||
Rect bounds() const
|
||||
{
|
||||
int rows = (_showHeader?1:0) + lastRecord - firstRecord + 1;
|
||||
return Rect(_margin.x, _margin.y, sum(_width), height * rows);
|
||||
return Rect(_location.x, _location.y, sum(_width), height * rows);
|
||||
}
|
||||
|
||||
/// Left and Top margins.
|
||||
void margin(Point pt)
|
||||
/// Left and Top point.
|
||||
void location(Point pt)
|
||||
{
|
||||
_margin.x = pt.x;
|
||||
_margin.y = pt.y;
|
||||
_location = pt;
|
||||
}
|
||||
/// ditto
|
||||
Point margin() const
|
||||
Point location() const
|
||||
{
|
||||
return _margin;
|
||||
return _location;
|
||||
}
|
||||
/// ditto
|
||||
deprecated void margin(Point pt)
|
||||
{
|
||||
location = pt;
|
||||
}
|
||||
/// ditto
|
||||
deprecated Point margin()
|
||||
{
|
||||
return location;
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -250,16 +267,26 @@ class TableRenderer(T...)
|
|||
struct WidthObject // Internal struct.
|
||||
{
|
||||
///
|
||||
this(ref int[] w)
|
||||
this(int[] w)
|
||||
{
|
||||
_arr = w;
|
||||
}
|
||||
|
||||
///
|
||||
/// Assign operator forwarding.
|
||||
void opIndexAssign(int value)
|
||||
{
|
||||
_arr[] = value;
|
||||
}
|
||||
/// ditto
|
||||
void opIndexAssign(int value, size_t i)
|
||||
{
|
||||
_arr[i] = value;
|
||||
}
|
||||
/// ditto
|
||||
void opSliceAssign(int value, size_t i, size_t j)
|
||||
{
|
||||
_arr[i..j] = value;
|
||||
}
|
||||
|
||||
///
|
||||
int opIndex(size_t i)
|
||||
|
@ -321,7 +348,7 @@ class TableRenderer(T...)
|
|||
|
||||
private:
|
||||
string _csv;
|
||||
Point _margin;
|
||||
Point _location;
|
||||
int _paddingX;
|
||||
int _paddingY;
|
||||
int _columns;
|
||||
|
@ -346,3 +373,577 @@ private:
|
|||
TextFormat _headerTextFormat;
|
||||
TextFormat _recordTextFormat;
|
||||
}
|
||||
|
||||
///
|
||||
class LineGraphRenderer(T...)
|
||||
if (is(T[0] == string) && T.length <= 17 || !is(T[0] == string) && T.length <= 16) // Supported number of colors is 16.
|
||||
{
|
||||
///
|
||||
this(string csv, int numRecords)
|
||||
{
|
||||
this(csv);
|
||||
_firstRecord = 0;
|
||||
_lastRecord = numRecords - 1;
|
||||
}
|
||||
/// ditto
|
||||
this(string csv) // deprecated
|
||||
{
|
||||
_csv = csv;
|
||||
_vZeroPos = VerticalZeroPosition.BOTTOM;
|
||||
_chartMargins = ChartMargins(50, 50, 50, 50);
|
||||
_backColor = Color.white;
|
||||
_plotAreaBoundsColor = Color.black;
|
||||
_plotAreaHeightOnDisplay = 100;
|
||||
_plotAreaLeftPadding = 20;
|
||||
_plotAreaRightPadding = 20;
|
||||
_plotAreaAndLegendSpanX = 50;
|
||||
_plotAreaAndHorizontalScaleSpanY = 10;
|
||||
_plotPointSize = 10;
|
||||
_plotLineColorPalette = [
|
||||
Color.black, // 0
|
||||
Color.blue, // 1
|
||||
Color.red, // 2
|
||||
Color.purple, // 3
|
||||
Color.yellowGreen, // 4
|
||||
Color.lightBlue, // 5
|
||||
Color(0xFF,0xC2,0x0E), // 6: Use Himawari color as yellow.
|
||||
Color.lightGray, // 7
|
||||
Color.black, // 8
|
||||
Color.darkBlue, // 9
|
||||
Color.darkRed, // 10
|
||||
Color.mediumPurple, // 11
|
||||
Color.darkGreen, // 12
|
||||
Color.darkSeaGreen, // 13
|
||||
Color.darkOrange, // 14
|
||||
Color.darkGray // 15
|
||||
];
|
||||
for (int i; i < T.length; i++)
|
||||
{
|
||||
_plotPointFormList ~= PlotPointForm.CIRCLE;
|
||||
}
|
||||
_legendWidth = 100;
|
||||
_legendLineHeight = 18;
|
||||
_hasVerticalScale = false;
|
||||
_verticalScaleWidth = 40;
|
||||
_verticalMaxScale = 100;
|
||||
_verticalScaleLineOuterSide = 5;
|
||||
_verticalScaleLineInnerSide = 5;
|
||||
_hasHorizontalScale = false;
|
||||
_horizontalScaleSpan = 50;
|
||||
_horizonScaleLineInnerSide = 5;
|
||||
_horizonScaleLineOuterSide = 5;
|
||||
_horizontalScaleHeight = 25;
|
||||
}
|
||||
|
||||
/// Draw records.
|
||||
void draw(Graphics g)
|
||||
{
|
||||
// Draw background.
|
||||
Rect backgroundRect = Rect(
|
||||
plotAreaBounds.x - _chartMargins.left,
|
||||
plotAreaBounds.y - _chartMargins.top,
|
||||
plotAreaBounds.width + _chartMargins.left + _chartMargins.right,
|
||||
plotAreaBounds.height + _chartMargins.top + _chartMargins.bottom
|
||||
);
|
||||
if (_showLegend)
|
||||
{
|
||||
backgroundRect.width = plotAreaBounds.width + _chartMargins.left + _legendWidth + _chartMargins.right;
|
||||
int legendBottom = _chartMargins.top + _legendLineHeight * (1 + cast(int)T.length) + _chartMargins.bottom;
|
||||
if (legendBottom > backgroundRect.bottom)
|
||||
backgroundRect.height = legendBottom;
|
||||
}
|
||||
if (_hasVerticalScale)
|
||||
{
|
||||
backgroundRect.x = plotAreaBounds.x - _chartMargins.left - _verticalScaleWidth,
|
||||
backgroundRect.width = plotAreaBounds.width + _chartMargins.left + _legendWidth + _chartMargins.right + _verticalScaleWidth;
|
||||
}
|
||||
if (_hasHorizontalScale)
|
||||
{
|
||||
backgroundRect.height += _horizontalScaleHeight + _plotAreaAndHorizontalScaleSpanY;
|
||||
}
|
||||
g.fillRectangle(new SolidBrush(_backColor), backgroundRect);
|
||||
// Draw bounds of plot area.
|
||||
g.drawRectangle(new Pen(_plotAreaBoundsColor), plotAreaBounds);
|
||||
// Draw vertical scale.
|
||||
int baseY = (_vZeroPos == VerticalZeroPosition.BOTTOM ? 0: _plotAreaHeightOnDisplay);
|
||||
double vRatio = cast(double)_plotAreaHeightOnDisplay / _verticalMaxScale;
|
||||
if (_hasVerticalScale)
|
||||
{
|
||||
enum LINE_HEIGHT = 12f;
|
||||
auto scaleList = iota(0, _verticalMaxScale, _verticalScaleSpan);
|
||||
Font f = new Font("MS Gothic", LINE_HEIGHT);
|
||||
int x = _originPoint.x - _verticalScaleWidth;
|
||||
int index;
|
||||
foreach (s; scaleList)
|
||||
{
|
||||
int y = cast(int)(baseY + _originPoint.y - LINE_HEIGHT / 2 + index * vRatio * _verticalScaleSpan * (_vZeroPos == VerticalZeroPosition.BOTTOM ? -1: 1));
|
||||
// Draw vertical scalse label.
|
||||
g.drawText(to!string(index * _verticalScaleSpan), f, Color.black, Rect(x, y, 100, 100));
|
||||
// Draw vertical scalse line.
|
||||
g.drawLine(
|
||||
new Pen(_plotAreaBoundsColor),
|
||||
_originPoint.x - _verticalScaleLineOuterSide,
|
||||
cast(int)(y + LINE_HEIGHT / 2 + (_vZeroPos == VerticalZeroPosition.BOTTOM ? -1: 0)),
|
||||
_originPoint.x + _verticalScaleLineInnerSide,
|
||||
cast(int)(y + LINE_HEIGHT / 2 + (_vZeroPos == VerticalZeroPosition.BOTTOM ? -1: 0))
|
||||
);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
// Draw horizontal scale.
|
||||
if (_hasHorizontalScale)
|
||||
{
|
||||
// Draw horizontal scale label.
|
||||
static if (is(T[0] == string))
|
||||
{{
|
||||
int i;
|
||||
foreach (label; csvReader!(Tuple!T)(_csv, null))
|
||||
{
|
||||
int x = _originPoint.x + _plotAreaLeftPadding + i * _horizontalScaleSpan - cast(int)_horizontalScaleHeight;
|
||||
int y = baseY + _originPoint.y + (_vZeroPos == VerticalZeroPosition.BOTTOM ? _plotAreaAndHorizontalScaleSpanY: -_plotAreaAndHorizontalScaleSpanY - _horizontalScaleHeight);
|
||||
g.drawText(
|
||||
label[0],
|
||||
new Font("MS Gothic", 12f),
|
||||
_plotAreaBoundsColor,
|
||||
Rect(x, y, _horizontalScaleSpan, _horizontalScaleHeight)
|
||||
);
|
||||
i++;
|
||||
}
|
||||
}}
|
||||
//
|
||||
for (int i; i < _lastRecord - _firstRecord + 1; i++)
|
||||
{
|
||||
int x = _originPoint.x + _plotAreaLeftPadding + i * _horizontalScaleSpan;
|
||||
int y = baseY + _originPoint.y;
|
||||
// Draw horizontal scale line.
|
||||
g.drawLine(
|
||||
new Pen(_plotAreaBoundsColor),
|
||||
x,
|
||||
y - _horizonScaleLineInnerSide * (_vZeroPos == VerticalZeroPosition.BOTTOM ? 1 : -1),
|
||||
x,
|
||||
y + _horizonScaleLineOuterSide * (_vZeroPos == VerticalZeroPosition.BOTTOM ? 1 : -1)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Draw legend.
|
||||
if (_showLegend)
|
||||
{
|
||||
int legendLine;
|
||||
foreach (i, value; csvReader!(Tuple!T)(_csv, null).header)
|
||||
{
|
||||
static if (is(T[0] == string))
|
||||
{
|
||||
if (i == 0) continue;
|
||||
}
|
||||
int x = plotAreaBounds.right + _plotAreaAndLegendSpanX + _plotPointSize;
|
||||
int y = plotAreaBounds.y + _legendLineHeight * cast(int)legendLine;
|
||||
g.drawText(to!string(value), new Font("MS Gothic", 12f), Color.black, Rect(x, y, 100, 100));
|
||||
_drawPlotPoint(
|
||||
g,
|
||||
new Pen(_plotLineColorPalette[legendLine]),
|
||||
_plotPointFormList[legendLine],
|
||||
_plotPointSize, x - _plotPointSize * 2, // Center X.
|
||||
y + _legendLineHeight / 2 // Center Y.
|
||||
);
|
||||
legendLine++;
|
||||
}
|
||||
}
|
||||
// Draw records.
|
||||
Tuple!T prevRecord;
|
||||
int x1 = _originPoint.x + _plotAreaLeftPadding;
|
||||
int x2;
|
||||
bool isFirstRecord = true;
|
||||
auto csvRange = csvReader!(Tuple!T)(_csv, null).drop(_firstRecord);
|
||||
foreach (currRecord; csvRange)
|
||||
{
|
||||
x2 = x1 + _horizontalScaleSpan;
|
||||
bool isFirstColumn = true;
|
||||
foreach (col, value; currRecord)
|
||||
{
|
||||
static if (!(is(T[0] == string) && col == 0))
|
||||
{
|
||||
int y1 = cast(int)(baseY + _originPoint.y + vRatio * prevRecord[col] * (_vZeroPos == VerticalZeroPosition.BOTTOM ? -1: 1));
|
||||
int y2 = cast(int)(baseY + _originPoint.y + vRatio * value * (_vZeroPos == VerticalZeroPosition.BOTTOM ? -1: 1));
|
||||
int seriesIndex = col - (is(T[0] == string)?1:0);
|
||||
if (!isFirstRecord)
|
||||
{
|
||||
g.drawLine(
|
||||
new Pen(_plotLineColorPalette[seriesIndex]),
|
||||
x1 - _horizontalScaleSpan,
|
||||
y1,
|
||||
x2 - _horizontalScaleSpan,
|
||||
y2
|
||||
);
|
||||
}
|
||||
_drawPlotPoint(
|
||||
g,
|
||||
new Pen(_plotLineColorPalette[seriesIndex]),
|
||||
_plotPointFormList[seriesIndex],
|
||||
_plotPointSize,
|
||||
x2 - _horizontalScaleSpan, // Center X.
|
||||
y2 // Center Y.
|
||||
);
|
||||
}
|
||||
isFirstColumn = false;
|
||||
}
|
||||
x1 = x1 + _horizontalScaleSpan;
|
||||
prevRecord = currRecord;
|
||||
isFirstRecord = false;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
Rect plotAreaBounds() const
|
||||
{
|
||||
return Rect(
|
||||
_originPoint.x,
|
||||
_originPoint.y + _plotAreaHeightOnDisplay * (_vZeroPos == VerticalZeroPosition.BOTTOM ? -1 : 1),
|
||||
cast(int)((_lastRecord - _firstRecord) * _horizontalScaleSpan) + _plotAreaLeftPadding + _plotAreaRightPadding,
|
||||
_plotAreaHeightOnDisplay
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
void originPoint(Point pt)
|
||||
{
|
||||
_originPoint = pt;
|
||||
}
|
||||
|
||||
///
|
||||
void relocate(Point pt)
|
||||
{
|
||||
final switch (_vZeroPos)
|
||||
{
|
||||
case VerticalZeroPosition.BOTTOM:
|
||||
int x = _chartMargins.left + _verticalScaleWidth + pt.x;
|
||||
int y = _chartMargins.top + _plotAreaHeightOnDisplay + pt.y;
|
||||
originPoint = Point(x, y);
|
||||
break;
|
||||
case VerticalZeroPosition.TOP:
|
||||
int x = _chartMargins.left + _verticalScaleWidth + pt.x;
|
||||
int y = _chartMargins.top + pt.y - _plotAreaHeightOnDisplay;
|
||||
originPoint = Point(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
void verticalZeroPosition(VerticalZeroPosition vZeroPos) // setter
|
||||
{
|
||||
_vZeroPos = vZeroPos;
|
||||
}
|
||||
|
||||
///
|
||||
void chartMargins(ChartMargins m)
|
||||
{
|
||||
_chartMargins = m;
|
||||
}
|
||||
|
||||
///
|
||||
void plotPointSize(int size)
|
||||
{
|
||||
_plotPointSize = size;
|
||||
}
|
||||
|
||||
///
|
||||
void showLegend(bool byes)
|
||||
{
|
||||
_showLegend = byes;
|
||||
}
|
||||
|
||||
///
|
||||
void plotLineColorPalette(Color[] colors)
|
||||
{
|
||||
_plotLineColorPalette = colors;
|
||||
}
|
||||
/// ditto
|
||||
Color[] plotLineColorPalette()
|
||||
{
|
||||
return _plotLineColorPalette;
|
||||
}
|
||||
|
||||
///
|
||||
void plotAreaAndLegendSpanX(int x)
|
||||
{
|
||||
_plotAreaAndLegendSpanX = x;
|
||||
}
|
||||
|
||||
///
|
||||
void plotAreaAndHorizontalScaleSpanY(int y)
|
||||
{
|
||||
_plotAreaAndHorizontalScaleSpanY = y;
|
||||
}
|
||||
|
||||
///
|
||||
void legendLineHeight(int h)
|
||||
{
|
||||
_legendLineHeight = h;
|
||||
}
|
||||
|
||||
///
|
||||
void legendWidth(int w)
|
||||
{
|
||||
_legendWidth = w;
|
||||
}
|
||||
|
||||
///
|
||||
void plotAreaRightPadding(int x)
|
||||
{
|
||||
_plotAreaRightPadding = x;
|
||||
}
|
||||
|
||||
///
|
||||
void plotAreaLeftPadding(int x)
|
||||
{
|
||||
_plotAreaLeftPadding = x;
|
||||
}
|
||||
|
||||
///
|
||||
void firstRecord(int i)
|
||||
{
|
||||
_firstRecord = i;
|
||||
}
|
||||
|
||||
///
|
||||
void lastRecord(int i)
|
||||
{
|
||||
_lastRecord = i;
|
||||
}
|
||||
|
||||
///
|
||||
void backColor(Color c)
|
||||
{
|
||||
_backColor = c;
|
||||
}
|
||||
|
||||
///
|
||||
void plotAreaBoundsColor(Color c)
|
||||
{
|
||||
_plotAreaBoundsColor = c;
|
||||
}
|
||||
|
||||
///
|
||||
void horizontalScaleSpan(int x)
|
||||
{
|
||||
_horizontalScaleSpan = x;
|
||||
}
|
||||
|
||||
///
|
||||
void verticalMaxScale(int m)
|
||||
{
|
||||
_verticalMaxScale = m;
|
||||
}
|
||||
|
||||
///
|
||||
void plotAreaHeightOnDisplay(int h)
|
||||
{
|
||||
_plotAreaHeightOnDisplay = h;
|
||||
}
|
||||
|
||||
///
|
||||
void hasVerticalScale(bool byes)
|
||||
{
|
||||
_hasVerticalScale = byes;
|
||||
}
|
||||
|
||||
///
|
||||
void hasHorizontalScale(bool byes)
|
||||
{
|
||||
_hasHorizontalScale = byes;
|
||||
}
|
||||
|
||||
///
|
||||
void verticalScaleSpan(int scale)
|
||||
{
|
||||
_verticalScaleSpan = scale;
|
||||
}
|
||||
|
||||
///
|
||||
void verticalScaleWidth(int w)
|
||||
{
|
||||
_verticalScaleWidth = w;
|
||||
}
|
||||
|
||||
///
|
||||
void verticalScaleLineOuterSide(int w)
|
||||
{
|
||||
_verticalScaleLineOuterSide = w;
|
||||
}
|
||||
|
||||
///
|
||||
void verticalScaleLineInnerSide(int w)
|
||||
{
|
||||
_verticalScaleLineInnerSide = w;
|
||||
}
|
||||
|
||||
///
|
||||
void horizonScaleLineInnerSide(int h)
|
||||
{
|
||||
_horizonScaleLineInnerSide = h;
|
||||
}
|
||||
|
||||
///
|
||||
void horizonScaleLineOuterSide(int h)
|
||||
{
|
||||
_horizonScaleLineOuterSide = h;
|
||||
}
|
||||
|
||||
///
|
||||
void horizontalScaleHeight(int h)
|
||||
{
|
||||
_horizontalScaleHeight = h;
|
||||
}
|
||||
|
||||
///
|
||||
void plotPointFormList(PlotPointForm[] forms)
|
||||
{
|
||||
_plotPointFormList = forms;
|
||||
}
|
||||
/// ditto
|
||||
PlotPointForm[] plotPointFormList()
|
||||
{
|
||||
return _plotPointFormList;
|
||||
}
|
||||
|
||||
private:
|
||||
string _csv;
|
||||
int _firstRecord;
|
||||
int _lastRecord;
|
||||
VerticalZeroPosition _vZeroPos;
|
||||
Point _originPoint;
|
||||
ChartMargins _chartMargins;
|
||||
bool _showLegend;
|
||||
int _legendLineHeight;
|
||||
int _legendWidth;
|
||||
Color _backColor;
|
||||
Color _plotAreaBoundsColor;
|
||||
int _plotAreaLeftPadding;
|
||||
int _plotAreaRightPadding;
|
||||
int _plotAreaHeightOnDisplay;
|
||||
int _plotAreaAndLegendSpanX;
|
||||
int _plotAreaAndHorizontalScaleSpanY;
|
||||
int _plotPointSize;
|
||||
Color[] _plotLineColorPalette;
|
||||
PlotPointForm[] _plotPointFormList;
|
||||
bool _hasHorizontalScale;
|
||||
int _horizontalScaleSpan;
|
||||
int _horizontalScaleHeight;
|
||||
int _horizonScaleLineInnerSide;
|
||||
int _horizonScaleLineOuterSide;
|
||||
bool _hasVerticalScale;
|
||||
int _verticalMaxScale;
|
||||
int _verticalScaleSpan;
|
||||
int _verticalScaleWidth;
|
||||
int _verticalScaleLineOuterSide;
|
||||
int _verticalScaleLineInnerSide;
|
||||
|
||||
///
|
||||
void _drawPlotPoint(Graphics g, Pen pen, PlotPointForm form, int plotPointSize, int centerX, int centerY)
|
||||
{
|
||||
final switch (form)
|
||||
{
|
||||
case PlotPointForm.CIRCLE:
|
||||
g.drawEllipse(
|
||||
pen,
|
||||
centerX - _plotPointSize / 2,
|
||||
centerY - _plotPointSize / 2,
|
||||
plotPointSize, // width
|
||||
plotPointSize // height
|
||||
);
|
||||
break;
|
||||
case PlotPointForm.RECTANGLE:
|
||||
g.drawRectangle(
|
||||
pen,
|
||||
centerX - _plotPointSize / 2,
|
||||
centerY - _plotPointSize / 2,
|
||||
plotPointSize, // width
|
||||
plotPointSize // height
|
||||
);
|
||||
break;
|
||||
case PlotPointForm.CROSS:
|
||||
g.drawLine(
|
||||
pen,
|
||||
centerX - _plotPointSize / 2,
|
||||
centerY - _plotPointSize / 2,
|
||||
centerX + _plotPointSize / 2 + 1,
|
||||
centerY + _plotPointSize / 2 + 1
|
||||
);
|
||||
g.drawLine(
|
||||
pen,
|
||||
centerX + _plotPointSize / 2,
|
||||
centerY - _plotPointSize / 2,
|
||||
centerX - _plotPointSize / 2 - 1,
|
||||
centerY + _plotPointSize / 2 + 1
|
||||
);
|
||||
break;
|
||||
case PlotPointForm.TRIANGLE:
|
||||
g.drawLine(
|
||||
pen,
|
||||
centerX,
|
||||
centerY - _plotPointSize * 2 / 3,
|
||||
centerX + _plotPointSize / 2,
|
||||
centerY + _plotPointSize / 3
|
||||
);
|
||||
g.drawLine(
|
||||
pen,
|
||||
centerX,
|
||||
centerY - _plotPointSize * 2 / 3,
|
||||
centerX - _plotPointSize / 2,
|
||||
centerY + _plotPointSize / 3
|
||||
);
|
||||
g.drawLine(
|
||||
pen,
|
||||
centerX - _plotPointSize / 2,
|
||||
centerY + _plotPointSize / 3,
|
||||
centerX + _plotPointSize / 2,
|
||||
centerY + _plotPointSize / 3
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
struct ChartMargins
|
||||
{
|
||||
int left; /// Left margin.
|
||||
int top; /// Top margin.
|
||||
int right; /// Right margin.
|
||||
int bottom; /// Bottom margin.
|
||||
|
||||
///
|
||||
this(int left, int top, int right, int bottom)
|
||||
{
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
this.right = right;
|
||||
this.bottom = bottom;
|
||||
}
|
||||
|
||||
///
|
||||
string toString() const
|
||||
{
|
||||
|
||||
string str = "[";
|
||||
str ~= to!string(left) ~ " ,";
|
||||
str ~= to!string(top) ~ " ,";
|
||||
str ~= to!string(right) ~ " ,";
|
||||
str ~= to!string(bottom) ~ "]";
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
enum VerticalZeroPosition
|
||||
{
|
||||
TOP,
|
||||
BOTTOM,
|
||||
}
|
||||
|
||||
///
|
||||
enum PlotPointForm
|
||||
{
|
||||
CIRCLE,
|
||||
RECTANGLE,
|
||||
CROSS,
|
||||
TRIANGLE,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue