diff --git a/examples/example1/src/example1.d b/examples/example1/src/example1.d index fb7bed77..b853b2f4 100644 --- a/examples/example1/src/example1.d +++ b/examples/example1/src/example1.d @@ -1092,9 +1092,53 @@ void main() tree.items.selectItem(tree.items.child(0)); tabs.addTab(treeLayout, "Tree"d); + //========================================================================== + // charts example + SimpleBarChart barChart1 = new SimpleBarChart("barChart1","SimpleBarChart Example"d); + barChart1.addBar(12.0, makeRGBA(255,0,0,0), "Red bar"d); + barChart1.addBar(24.0, makeRGBA(0,255,0,0), "Green bar"d); + barChart1.addBar(5.0, makeRGBA(0,0,255,0), "Blue bar"d); + barChart1.addBar(12.0, makeRGBA(230,126,34,0), "Orange bar"d); + //barChart1.layoutWidth = FILL_PARENT; + //barChart1.layoutHeight = FILL_PARENT; + + SimpleBarChart barChart2 = new SimpleBarChart("barChart2","SimpleBarChart Example - long descriptions"d); + barChart2.addBar(12.0, makeRGBA(255,0,0,0), "Red bar\n(12.0)"d); + barChart2.addBar(24.0, makeRGBA(0,255,0,0), "Green bar\n(24.0)"d); + barChart2.addBar(5.0, makeRGBA(0,0,255,0), "Blue bar\n(5.0)"d); + barChart2.addBar(12.0, makeRGBA(230,126,34,0), "Orange bar\n(12.0)\nlong long long description added here"d); - + SimpleBarChart barChart3 = new SimpleBarChart("barChart3","SimpleBarChart Example with axis ratio 0.3"d); + barChart3.addBar(12.0, makeRGBA(255,0,0,0), "Red bar"d); + barChart3.addBar(24.0, makeRGBA(0,255,0,0), "Green bar"d); + barChart3.addBar(5.0, makeRGBA(0,0,255,0), "Blue bar"d); + barChart3.addBar(12.0, makeRGBA(230,126,34,0), "Orange bar"d); + barChart3.axisRatio = 0.3; + + SimpleBarChart barChart4 = new SimpleBarChart("barChart4","SimpleBarChart Example with axis ratio 1.3"d); + barChart4.addBar(12.0, makeRGBA(255,0,0,0), "Red bar"d); + barChart4.addBar(24.0, makeRGBA(0,255,0,0), "Green bar"d); + barChart4.addBar(5.0, makeRGBA(0,0,255,0), "Blue bar"d); + barChart4.addBar(12.0, makeRGBA(230,126,34,0), "Orange bar"d); + barChart4.axisRatio = 1.3; + + HorizontalLayout chartsLayout = new HorizontalLayout("CHARTS"); + chartsLayout.layoutWidth = FILL_PARENT; + chartsLayout.layoutHeight = FILL_PARENT; + + VerticalLayout chartColumn1 = new VerticalLayout(); + VerticalLayout chartColumn2 = new VerticalLayout(); + + chartColumn1.addChild(barChart1); + chartColumn1.addChild(barChart2); + chartsLayout.addChild(chartColumn1); + chartColumn2.addChild(barChart3); + chartColumn2.addChild(barChart4); + chartsLayout.addChild(chartColumn2); + tabs.addTab(chartsLayout, "Charts"d); + static if (BACKEND_GUI) { + tabs.addTab((new SampleAnimationWidget("tab6")).layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT), "TAB_ANIMATION"c); CanvasWidget canvas = new CanvasWidget("canvas"); diff --git a/src/dlangui/widgets/charts.d b/src/dlangui/widgets/charts.d new file mode 100644 index 00000000..5e59c24c --- /dev/null +++ b/src/dlangui/widgets/charts.d @@ -0,0 +1,482 @@ +// Written in the D programming language. + +/** +This module contains charts widgets implementation. +Currently only SimpleBarChart. + + +Synopsis: + +---- +import dlangui.widgets.charts; + +// creation of simple bar chart +SimpleBarChart chart = new SimpleBarChart("chart"); + +// add bars +chart.addBar(12.2, makeRGBA(255, 0, 0, 0), "new bar"c); + +// update bar with index 0 +chart.updateBar(0, 10, makeRGBA(255, 255, 0, 0), "new bar updated"c); +chart.updateBar(0, 20); + +// remove bars with index 0 +chart.removeBar(0, 20); + +// change title +chart.title = "new title"d; + +// change min axis ratio +chart.axisRatio = 0.3; // y axis length will be 0.3 of x axis + +---- + +Copyright: Andrzej Kilijański, 2017 +License: Boost License 1.0 +Authors: Andrzej Kilijański, and3md@gmail.com +*/ + +module dlangui.widgets.charts; + +import dlangui.widgets.widget; +import std.math; +import std.algorithm.comparison; +import std.algorithm : remove; +import std.conv; + +class SimpleBarChart : Widget { + + this(string ID = null) { + super(ID); + clickable = false; + focusable = false; + trackHover = false; + styleId = "SIMPLE_BAR_CHART"; + _axisX.arrowSize = 1; + title = "New chart"d; + measureTextToSetWidgetSize(); + } + + this(string ID, string titleResourceId) { + this(ID); + title = UIString.fromId(titleResourceId); + } + + this(string ID, dstring title) { + this(ID); + this.title = UIString.fromRaw(title); + } + + this(string ID, UIString title) { + this(ID); + this.title = title; + } + + struct BarData { + double y; + UIString title; + private Point _titleSize; + uint color; + + this (double y, uint color, UIString title) { + this.y = y; + this.color = color; + this.title = title; + } + } + + protected BarData[] _bars; + protected double _maxY = 0; + + size_t barCount() { + return _bars.length; + } + + void addBar(double y, uint color, UIString barTitle) { + if (y < 0) + return; // current limitation only positive values + _bars ~= BarData(y, color, barTitle); + if (y > _maxY) + _maxY = y; + requestLayout(); + } + + void addBar(double y, uint color, string barTitle) { + addBar(y, color, UIString.fromId(barTitle)); + } + + void addBar(double y, uint color, dstring barTitle) { + addBar(y, color, UIString.fromRaw(barTitle)); + } + + void removeBar(size_t index) { + _bars = remove(_bars, index); + // update _maxY + _maxY = 0; + foreach (ref bar ; _bars) { + if (bar.y > _maxY) + _maxY = bar.y; + } + requestLayout(); + } + + void updateBar(size_t index, double y, uint color, string barTitle) { + updateBar(index, y, color, UIString.fromId(barTitle)); + } + + void updateBar(size_t index, double y, uint color, dstring barTitle) { + updateBar(index, y, color, UIString.fromRaw(barTitle)); + } + + void updateBar(size_t index, double y, uint color, UIString barTitle) { + if (y < 0) + return; // current limitation only positive values + _bars[index].y = y; + _bars[index].color = color; + _bars[index].title = barTitle; + + // update _maxY + _maxY = 0; + foreach (ref bar ; _bars) { + if (bar.y > _maxY) + _maxY = bar.y; + } + requestLayout(); + } + + void updateBar(size_t index, double y) { + if (y < 0) + return; // curent limitation only positive values + _bars[index].y = y; + + // update _maxY + _maxY = 0; + foreach (ref bar ; _bars) { + if (bar.y > _maxY) + _maxY = bar.y; + } + requestLayout(); + } + + protected UIString _title; + protected bool _showTitle = true; + protected Point _titleSize; + protected int _marginAfterTitle = 2; + + /// set title to show + @property Widget title(string s) { + return title(UIString.fromId(s)); + } + + @property Widget title(dstring s) { + return title(UIString.fromRaw(s)); + } + + /// set title to show + @property Widget title(UIString s) { + _title = s; + measureTitleSize(); + if (_showTitle) + requestLayout(); + return this; + } + + /// get title value + @property dstring title() { + return _title; + } + + /// show title? + @property bool showTitle() { + return _showTitle; + } + + @property void showTitle(bool show) { + if (_showTitle != show) { + _showTitle = show; + requestLayout(); + } + } + + override protected void handleFontChanged() { + measureTitleSize(); + measureTextToSetWidgetSize(); + } + + protected void measureTitleSize() { + FontRef font = font(); + _titleSize = font.textSize(_title, MAX_WIDTH_UNSPECIFIED, 4, 0, textFlags); //todo: more than one line title support + } + + @property uint chartBackgroundColor() {return ownStyle.customColor("chart_background_color"); } + + @property Widget chartBackgroundColor(uint newColor) { + ownStyle.setCustomColor("chart_background_color",newColor); + invalidate(); + return this; + } + + @property uint chartAxisColor() {return ownStyle.customColor("chart_axis_color"); } + + @property Widget chartAxisColor(uint newColor) { + ownStyle.setCustomColor("chart_axis_color",newColor); + invalidate(); + return this; + } + + @property uint chartSegmentTagColor() {return ownStyle.customColor("chart_segment_tag_color"); } + + @property Widget chartSegmentTagColor(uint newColor) { + ownStyle.setCustomColor("chart_segment_tag_color",newColor); + invalidate(); + return this; + } + + struct AxisData { + Point maxDescriptionSize = Point(30,20); + int thickness = 1; + int arrowSize = 20; + int segmentTagLength = 4; + int zeroValueDist = 3; + int lengthFromZeroToArrow = 200; + } + + AxisData _axisX; + AxisData _axisY; + + protected int _axisYMaxValueDescWidth = 30; + protected int _axisYAvgValueDescWidth = 30; + + protected double _axisRatio = 0.6; + + @property double axisRatio() { + return _axisRatio; + } + + @property void axisRatio(double newRatio) { + _axisRatio = newRatio; + requestLayout(); + } + + protected int _minBarWidth = 10; + protected int _barWidth = 10; + protected int _barDistance = 3; + + protected int _axisXMinWfromZero = 150; + protected int _axisYMinDescWidth = 30; + + protected dstring _textToSetDescLineSize = "aaaaaaaaaa"; + protected Point _measuredTextToSetDescLineSize; + + @property dstring textToSetDescLineSize() { + return _textToSetDescLineSize; + } + + @property void textToSetDescLineSize(dstring newText) { + _textToSetDescLineSize = newText; + measureTextToSetWidgetSize(); + requestLayout(); + } + + private int[] _charWidths; + protected Point measureTextToSetWidgetSize() { + FontRef font = font(); + _charWidths.length = _textToSetDescLineSize.length; + int charsMeasured = font.measureText(_textToSetDescLineSize, _charWidths, MAX_WIDTH_UNSPECIFIED, 4); + _measuredTextToSetDescLineSize.x = charsMeasured > 0 ? _charWidths[charsMeasured - 1]: 0; + _measuredTextToSetDescLineSize.y = font.height; + return _measuredTextToSetDescLineSize; + } + + override void measure(int parentWidth, int parentHeight) { + FontRef font = font(); + + int mWidth = minWidth; + int mHeight = minHeight; + + int chartW = 0; + int chartH = 0; + + _axisY.maxDescriptionSize = measureAxisYDesc(); + + int usedWidth = _axisY.maxDescriptionSize.x + _axisY.thickness + _axisY.segmentTagLength + _axisX.zeroValueDist + margins.left + padding.left + margins.right + padding.right + _axisX.arrowSize; + + int currentMinBarWidth = max(_minBarWidth, _measuredTextToSetDescLineSize.x); + _axisX.maxDescriptionSize.y = _measuredTextToSetDescLineSize.y; + + // axis length + _axisX.lengthFromZeroToArrow = cast(uint) barCount * (currentMinBarWidth + _barDistance); + + if (_axisX.lengthFromZeroToArrow < _axisXMinWfromZero) { + _axisX.lengthFromZeroToArrow = _axisXMinWfromZero; + if (barCount > 0) + _barWidth = cast (int) ((_axisX.lengthFromZeroToArrow - (_barDistance * barCount)) / barCount); + } + + // minWidth and minHeight check + + if (minWidth > _axisX.lengthFromZeroToArrow + usedWidth) { + _axisX.lengthFromZeroToArrow = minWidth-usedWidth; + if (barCount > 0) + _barWidth = cast (int) ((_axisX.lengthFromZeroToArrow - (_barDistance * barCount)) / barCount); + } + + // width FILL_PARENT support + if (parentWidth != SIZE_UNSPECIFIED && layoutWidth == FILL_PARENT) { + if (_axisX.lengthFromZeroToArrow < parentWidth - usedWidth) { + _axisX.lengthFromZeroToArrow = parentWidth - usedWidth; + if (barCount > 0) + _barWidth = cast (int) ((_axisX.lengthFromZeroToArrow - (_barDistance * barCount)) / barCount); + } + } + + + // initialize axis y length + _axisY.lengthFromZeroToArrow = cast(int) round(_axisRatio * _axisX.lengthFromZeroToArrow); + + // is axis Y enought long + int usedHeight = _axisX.maxDescriptionSize.y + _axisX.thickness + _axisX.segmentTagLength + _axisY.zeroValueDist + ((_showTitle) ? _titleSize.y + _marginAfterTitle : 0) + margins.top + padding.top + margins.bottom + padding.bottom + _axisY.arrowSize; + if (minHeight > _axisY.lengthFromZeroToArrow + usedHeight) { + _axisY.lengthFromZeroToArrow = minHeight - usedHeight; + _axisX.lengthFromZeroToArrow = cast (int) round(_axisY.lengthFromZeroToArrow / _axisRatio); + } + + // height FILL_PARENT support + if (parentHeight != SIZE_UNSPECIFIED && layoutHeight == FILL_PARENT) { + if (_axisY.lengthFromZeroToArrow < parentHeight - usedHeight) + _axisY.lengthFromZeroToArrow = parentHeight - usedHeight; + } + + if (barCount > 0) + _barWidth = cast (int) ((_axisX.lengthFromZeroToArrow - (_barDistance * barCount)) / barCount); + + // compute X axis max description height + Log.d("barW ",_barWidth); + _axisX.maxDescriptionSize = measureAxisXDesc(); + + // compute chart size + chartW = _axisY.maxDescriptionSize.x + _axisY.thickness + _axisY.segmentTagLength + _axisX.zeroValueDist + _axisX.lengthFromZeroToArrow + _axisX.arrowSize; + if (_showTitle && chartW < _titleSize.y) + chartW = _titleSize.y; + + chartH = _axisX.maxDescriptionSize.y + _axisX.thickness + _axisX.segmentTagLength + _axisY.zeroValueDist + _axisY.lengthFromZeroToArrow + ((_showTitle) ? _titleSize.y + _marginAfterTitle : 0) + _axisY.arrowSize; + measuredContent(parentWidth, parentHeight, chartW, chartH); + } + + + protected Point measureAxisXDesc() { + Point sz; + foreach (ref bar ; _bars) { + bar._titleSize = font.measureMultilineText(bar.title, 0, _barWidth, 4, 0, textFlags); + if (sz.y < bar._titleSize.y) + sz.y = bar._titleSize.y; + if (sz.x < bar._titleSize.x) + sz.x = bar._titleSize.y; + } + return sz; + } + + protected Point measureAxisYDesc() { + int maxDescWidth = _axisYMinDescWidth; + double currentMaxValue = _maxY; + if (approxEqual(_maxY, 0, 0.0000001, 0.0000001)) + currentMaxValue = 100; + + Point sz = font.textSize(to!dstring(currentMaxValue), MAX_WIDTH_UNSPECIFIED, 4, 0, textFlags); + if (maxDescWidth + diff --git a/views/res/theme_default.xml b/views/res/theme_default.xml index f6fc43f1..c3b1fa70 100644 --- a/views/res/theme_default.xml +++ b/views/res/theme_default.xml @@ -525,6 +525,16 @@ + +