diff --git a/README.md b/README.md index 98c8f43..ec481fa 100644 --- a/README.md +++ b/README.md @@ -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. ![screen shot](./examples/richtextbox/image/screenshot.png "screen shot") ![screen shot](./examples/dclock/image/screenshot.png "screen shot") ![screen shot](./examples/tablerenderer/image/screenshot.png "screen shot") +![screen shot](./examples/linegraphrenderer/image/screenshot.png "screen shot") ## Build and Install (dfl.lib and dfl_debug.lib) ### 1. Set environment variables diff --git a/examples/linegraphrenderer/.gitignore b/examples/linegraphrenderer/.gitignore new file mode 100644 index 0000000..60a5f2a --- /dev/null +++ b/examples/linegraphrenderer/.gitignore @@ -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 diff --git a/examples/linegraphrenderer/.vscode/launch.json b/examples/linegraphrenderer/.vscode/launch.json new file mode 100644 index 0000000..e6e878e --- /dev/null +++ b/examples/linegraphrenderer/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/examples/linegraphrenderer/.vscode/tasks.json b/examples/linegraphrenderer/.vscode/tasks.json new file mode 100644 index 0000000..d571fe2 --- /dev/null +++ b/examples/linegraphrenderer/.vscode/tasks.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/examples/linegraphrenderer/README.md b/examples/linegraphrenderer/README.md new file mode 100644 index 0000000..b1a86ca --- /dev/null +++ b/examples/linegraphrenderer/README.md @@ -0,0 +1,2 @@ +# Screen Shot +![screen shot](./image/screenshot.png "screen shot") diff --git a/examples/linegraphrenderer/dub.json b/examples/linegraphrenderer/dub.json new file mode 100644 index 0000000..b09eec4 --- /dev/null +++ b/examples/linegraphrenderer/dub.json @@ -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"] +} \ No newline at end of file diff --git a/examples/linegraphrenderer/image/screenshot.png b/examples/linegraphrenderer/image/screenshot.png new file mode 100644 index 0000000..9c4852e Binary files /dev/null and b/examples/linegraphrenderer/image/screenshot.png differ diff --git a/examples/linegraphrenderer/linegraphrenderer.code-workspace b/examples/linegraphrenderer/linegraphrenderer.code-workspace new file mode 100644 index 0000000..7e696bc --- /dev/null +++ b/examples/linegraphrenderer/linegraphrenderer.code-workspace @@ -0,0 +1,12 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "d.projectImportPaths": [ + "..\\..\\..\\dfl\\source" + ] + } +} \ No newline at end of file diff --git a/examples/linegraphrenderer/shell.bat b/examples/linegraphrenderer/shell.bat new file mode 100644 index 0000000..49ab56b --- /dev/null +++ b/examples/linegraphrenderer/shell.bat @@ -0,0 +1,3 @@ +set dmd_path=c:\d\dmd2\windows +set dmc_path=c:\dmc\dm +cmd diff --git a/examples/linegraphrenderer/source/linegraphrenderer_sample.d b/examples/linegraphrenderer/source/linegraphrenderer_sample.d new file mode 100644 index 0000000..6f0073c --- /dev/null +++ b/examples/linegraphrenderer/source/linegraphrenderer_sample.d @@ -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()); +} diff --git a/examples/tablerenderer/image/screenshot.png b/examples/tablerenderer/image/screenshot.png index e1bdb7a..a978a2b 100644 Binary files a/examples/tablerenderer/image/screenshot.png and b/examples/tablerenderer/image/screenshot.png differ diff --git a/examples/tablerenderer/source/tablerenderer_sample.d b/examples/tablerenderer/source/tablerenderer_sample.d index fd9fe0e..4e54cbf 100644 --- a/examples/tablerenderer/source/tablerenderer_sample.d +++ b/examples/tablerenderer/source/tablerenderer_sample.d @@ -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; diff --git a/source/dfl/chart.d b/source/dfl/chart.d index bad57b7..746b68c 100644 --- a/source/dfl/chart.d +++ b/source/dfl/chart.d @@ -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, +} \ No newline at end of file