How to place the text values inside in MPAndroidChart circle? - android

I am developing a chart based application, I am using MPAndroidChart library, I need to place the text value inside of circle, i tried to display,Thanks for if any suggestions related this,
i attached a screenshot related to that issue. I need to be do like this
but i get like this image:
Thanks Again for helping this issue,
ArrayList<Entry> e1 = new ArrayList<Entry>();
float[] values = new float[]{48, 59, 79, 29, 39, 50, 60};
for (int i = 0; i < values.length; i++) {
e1.add(new Entry(values[i], i, "line3"));
}
int[] color = {Color.parseColor("#D13385"), Color.parseColor("#37D04E"), Color.parseColor("#33D1D1"), Color.parseColor("#D1C933")};
LineDataSet d1 = new LineDataSet(e1, "" + cnt);
d1.setColors(color);
d1.setLineWidth(3.0f);
d1.setCircleSize(7.0f);
d1.setDrawValues(true);
d1.setCircleColor(Color.parseColor("#891e9a"));
d1.setCircleColorHole(Color.parseColor("#891e9a"));
d1.setDrawHighlightIndicators(false);
d1.setDrawFilled(false);
d1.setFillAlpha(20);
d1.setHighlightLineWidth(50f);
d1.setValueTextSize(10f);

Currently it is not possible to change the position where the values are drawn by default. You will have to modify the library to get that behaviour.

It is a bit "hacky", but I've managed to achieve such layout You've provided by creating two sets of data and attaching them to same chart. One set (lets call it "dots") contains your data needed to be displayed as dots. The second one ("lines") is a bit offset downwards (y value minus some experimentally picked value). Now you can set no line displaying for "lines" and a lines for "dots", no values labels for "dots" and white labels for "lines" and by experimentally moving your y values back and forth you can achieve overlaying values labels from one chart on top of another ("dots" will be covered by "lines" values).
UPDATE:
Actually, I've an answer more elegant, that I've provided! Use Highlight[] and create array of highlights.
highlihts = new Highlight[values_dots.size()];
for (int i = 0; i < values_dots.size(); i++) {
Highlight h = new Highlight(values_dots.get(i).getX(),values_dots.get(i).getY(), 0);
highlihts[i] = h;
}
chart.highlightValues(highlihts);
In CustomMarkerView class position marker like so:
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -(getHeight() / 2));
}
Boom
P.S. Philipp Jahoda, awesome library!

there was an easy way to do this
custom maker view
Marker view
public class MyMarkerView extends MarkerView {
private final TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
tvContent = findViewById(R.id.tvContent);
}
// runs every time the MarkerView is redrawn, can be used to update the
// content (user-interface)
#SuppressLint("SetTextI18n")
#Override
public void refreshContent(Entry e, Highlight highlight) {
if (e instanceof CandleEntry) {
CandleEntry ce = (CandleEntry) e;
tvContent.setText(Utils.formatNumber(ce.getHigh(), 0, true)+(ce.getData()));
} else {
tvContent.setText(Utils.formatNumber(e.getY(), 0, true)+"\n "+(e.getData()));
}
super.refreshContent(e, highlight);
}
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}
and lastly in your activity
values.add(new Entry(i, val,"Custom message per value"));
Preview be like
example screen shot

there was an easy way to do this
custom maker view
<TextView
android:id="#+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="7dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text=""
android:textSize="12sp"
android:textColor="#android:color/white"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
Marker view class
public class MyMarkerView extends MarkerView {
private final TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
tvContent = findViewById(R.id.tvContent);
}
// runs every time the MarkerView is redrawn, can be used to update the
// content (user-interface)
#SuppressLint("SetTextI18n")
#Override
public void refreshContent(Entry e, Highlight highlight) {
if (e instanceof CandleEntry) {
CandleEntry ce = (CandleEntry) e;
tvContent.setText(Utils.formatNumber(ce.getHigh(), 0, true)+(ce.getData()));
} else {
tvContent.setText(Utils.formatNumber(e.getY(), 0, true)+"\n "+(e.getData()));
}
super.refreshContent(e, highlight);
}
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}
and lastly in your activity
values.add(new Entry(i, val,"Custom message per value"));
Preview be like
example screen shot

There are two possibilities:
(1) Not so good: Shift the y-value of the label position
Two Data sets one for text and one for the line (including circles)
Modifiy the y-position for the text value with a constant offset
Pro: Easy
Con: The offset is not always constant (see offset is not always similar)
(2) Better: Override the drawValues method from LineChartRenderer
In LineChartRenderer.java -> drawValues the text is vertically shifted by this line:
drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
So to get rid of the "- valOffset":
1.Override the drawValues method
Create a new java file "CenteredTextLineChartRenderer.java" and override method drawValues from LineChartRenderer
2.Modify the y-valOffset to y+textHeight*0.35f
Add float textHeight = dataSet.getValueTextSize();
public class CenteredTextLineChartRenderer extends LineChartRenderer {
public CenteredTextLineChartRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
//Modified drawValues Method
// Center label on coordinate instead of applying a valOffset
#Override
public void drawValues(Canvas c) {
if (isDrawingValuesAllowed(mChart)) {
List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
float textHeight = dataSet.getValueTextSize();
if (!shouldDrawValues(dataSet) || dataSet.getEntryCount() < 1)
continue;
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
// make sure the values do not interfear with the circles
int valOffset = (int) (dataSet.getCircleRadius() * 1.75f);
if (!dataSet.isDrawCirclesEnabled())
valOffset = valOffset / 2;
mXBounds.set(mChart, dataSet);
float[] positions = trans.generateTransformedValuesLine(dataSet, mAnimator.getPhaseX(), mAnimator
.getPhaseY(), mXBounds.min, mXBounds.max);
ValueFormatter formatter = dataSet.getValueFormatter();
MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
for (int j = 0; j < positions.length; j += 2) {
float x = positions[j];
float y = positions[j + 1];
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsLeft(x) || !mViewPortHandler.isInBoundsY(y))
continue;
Entry entry = dataSet.getEntryForIndex(j / 2 + mXBounds.min);
if (dataSet.isDrawValuesEnabled()) {
//drawValue(c, formatter.getPointLabel(entry), x, y - valOffset, dataSet.getValueTextColor(j / 2));
drawValue(c, formatter.getPointLabel(entry), x, y+textHeight*0.35f, dataSet.getValueTextColor(j / 2));
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
Utils.drawImage(
c,
icon,
(int)(x + iconsOffset.x),
(int)(y + iconsOffset.y),
icon.getIntrinsicWidth(),
icon.getIntrinsicHeight());
}
}
MPPointF.recycleInstance(iconsOffset);
}
}
}
}
3.Set your own LineChart renderer to your modified drawValues class
LineChart mChart = (LineChart) mainActivity.findViewById(R.id.LineChart);
mChart.setRenderer(new CenteredTextLineChartRenderer(mChart,mChart.getAnimator(),mChart.getViewPortHandler()));
Run your code and manually adapt the 0.35f offset in your CenteredTextLineChartRenderer class
Now your text is always vertically centered!
IMPORTANT: With deleting the valOffset your label is not vertically centered as the text anchor is not in the center of your text label. So you have to insert a manual offset "textHeight*0.35f" (just try it out). But the big advantage of method (2) is that the text is always centered with the same offset also for example in landscape mode and on other screen sizes...

Related

MPAndroidChart - How to display text to the left of LimitLine?

I'm trying to display text on the left of the LimitLine like this:
However these are the only options I'm getting for setting the position of the Label for limit line.
I'm using LimitLine.LimitLabelPosition.LEFT_TOP and it only displays the Label above the Limit line.
YAxis leftAxis = mChart.getAxisLeft();
LimitLine minimumLimit = new LimitLine(50f, "Minimum Limit");
minimumLimit.setLineWidth(0.5f);
minimumLimit.setTextColor(ContextCompat.getColor(this, R.color.white_60_opacity));
minimumLimit.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
leftAxis.addLimitLine(minimumLimit);
How do I display the LimitLine's Label to the left of the LimitLine?
Edit:
I have also tried used the methods .setXOffset(50f) and .setYOffset(50f) but this only shifts the position of the label and not the line minimumLimit.
You can achieve this by using a custom YAxisRenderer with a little modification of the override method public void renderLimitLines(Canvas c).
The modifications needed for this purpose are:
1.To calculate the label width of each limit line to be able to move the limit line to the correct x position like below:
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
2.To draw the label to the new x,y position something like this:
c.drawText(label, mViewPortHandler.contentLeft() + xOffset, pts[1]+l.getYOffset(), mLimitLinePaint);
Below is a custom MyYAxisRenderer containing the above modifications:
public class MyYAxisRenderer extends YAxisRenderer {
private final Paint textPaint;
public MyYAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
super(viewPortHandler, yAxis, trans);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
public void renderLimitLines(Canvas c) {
List<LimitLine> limitLines = mYAxis.getLimitLines();
if (limitLines == null || limitLines.size() <= 0)
return;
float[] pts = mRenderLimitLinesBuffer;
pts[0] = 0;
pts[1] = 0;
Path limitLinePath = mRenderLimitLines;
limitLinePath.reset();
for (int i = 0; i < limitLines.size(); i++) {
LimitLine l = limitLines.get(i);
if (!l.isEnabled())
continue;
int clipRestoreCount = c.save();
mLimitLineClippingRect.set(mViewPortHandler.getContentRect());
mLimitLineClippingRect.inset(0.f, -l.getLineWidth());
c.clipRect(mLimitLineClippingRect);
mLimitLinePaint.setStyle(Paint.Style.STROKE);
mLimitLinePaint.setColor(l.getLineColor());
mLimitLinePaint.setStrokeWidth(l.getLineWidth());
mLimitLinePaint.setPathEffect(l.getDashPathEffect());
pts[1] = l.getLimit();
mTrans.pointValuesToPixel(pts);
limitLinePath.moveTo(mViewPortHandler.contentLeft()+getLabelTextWidth(l), pts[1]);
limitLinePath.lineTo(mViewPortHandler.contentRight(), pts[1]);
c.drawPath(limitLinePath, mLimitLinePaint);
limitLinePath.reset();
String label = l.getLabel();
// if drawing the limit-value label is enabled
if (label != null && !label.equals("")) {
mLimitLinePaint.setStyle(l.getTextStyle());
mLimitLinePaint.setPathEffect(null);
mLimitLinePaint.setColor(l.getTextColor());
mLimitLinePaint.setTypeface(l.getTypeface());
mLimitLinePaint.setStrokeWidth(0.5f);
mLimitLinePaint.setTextSize(l.getTextSize());
final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label);
float xOffset = getLimitLineXOffset(l);
float yOffset = l.getLineWidth() + labelLineHeight + l.getYOffset();
final LimitLine.LimitLabelPosition position = l.getLabelPosition();
//draw the label on the left in the same y position of the limit line
mLimitLinePaint.setTextAlign(Paint.Align.LEFT);
c.drawText(label,
mViewPortHandler.contentLeft() + xOffset,
pts[1]+l.getYOffset(), mLimitLinePaint);
}
c.restoreToCount(clipRestoreCount);
}
}
private float getLimitLineXOffset(LimitLine l){
return Utils.convertDpToPixel(4f) + l.getXOffset();
}
private float getLabelTextWidth(LimitLine l) {
String label = l.getLabel();
if (label != null && !label.equals("")) {
textPaint.setStyle(l.getTextStyle());
textPaint.setPathEffect(null);
textPaint.setColor(l.getTextColor());
textPaint.setTypeface(l.getTypeface());
textPaint.setStrokeWidth(0.5f);
textPaint.setTextSize(l.getTextSize());
int textWidth = Utils.calcTextWidth(textPaint, label);
float xOffset = getLimitLineXOffset(l);
return textWidth + (xOffset*2);
}
return 0;
}
}
In the above renderer i have added two helper functions one for the calculation of the label text width private float getLabelTextWidth(LimitLine l) for a specific limit line and one to get the x offset of each limit line private float getLimitLineXOffset(LimitLine l) which you can modify based on your needs.
And you can use the above Renderer like the below:
lineChart.setRendererLeftYAxis(new MyYAxisRenderer(lineChart.getViewPortHandler(), lineChart.getAxisLeft(), lineChart.getTransformer(YAxis.AxisDependency.LEFT)));
Result:
Note: This was tested with v3.1.0 ('com.github.PhilJay:MPAndroidChart:v3.1.0')

MPAndroidChart. How to use data sets with different data types on single chart?

I am trying to combine multiple line charts in a single view but i cannot solve problem with different data types. I've tried to use min-max normalization to scale values from different data sets but when i want to show selected value in custom marker view, i cannot convert it back to initial format.
I've tried to use different ValueFormatters for all my data sets but i have not achieved anything.
Normalization code:
private List<Entry> normalizeEntry(List<Entry> entries, int min, int max) {
for (int i = 0; i < entries.size(); i++) {
float lastValue = entries.get(i).getY();
float newValue = normalize(lastValue, min, max, 0, 100);
entries.set(i, new Entry(i, newValue));
}
return entries;
}
Picture which show what i want to achieve:
I was able to solve my problem by adding a parameter corresponding to the value before normalization to the constructor of the Entry object.
before:
new Entry(xValue, normalizedValue);
after:
new Entry(xValue, normalizedValue, initialValueWithOutputFormat);
Example:
entries.add(new Entry(i, normalizedValues.get(i), String.format("%.2f m", inputValues.get(i))));
Then, I can easily update custom marker like in following code:
public class CustomMarkerView extends MarkerView {
private TextView tvContent;
public CustomMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
// this markerview only displays a textview
tvContent = findViewById(R.id.tvContent);
}
#Override
public void refreshContent(Entry e, Highlight highlight) {
tvContent.setText(String.valueOf(e.getData()));
super.refreshContent(e, highlight);
}
#Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}

How to put customized value&image per bar in MPAndroidChart?

Background
I'm using MPAndroidChart to show a relatively simple bar chart.
The problem
There are 2 things I need to set, that I can't figure out how to customize:
Instead of simple values, I need to add text per each bar, which by itself is also styled.
On top of each bar, I need to put various types of drawable that cover it in width (for example blue with height of 2dp in one bar, or yellow gradient with same height on another bar).
Here's a demonstration of what I need to do:
What I've found
I've checked the docs, but only thing I've found so far is to put the values above the bar, using setDrawValueAboveBar :
https://github.com/PhilJay/MPAndroidChart/wiki/Specific-Chart-Settings-&-Styling
I know I can also add icons, by using setDrawIcons , but this doesn't seem to work for drawables that should take entire bar width.
The questions
As I wrote, I'd like to know if the above are possible, and how:
How can I set a customized, styled value above each bar ?
How can I set a different drawable to "sit" on top of each bar?
If #2 is not possible (and maybe even if it is), is it possible to set a drawable to be the bar itself? For example, some bars would be a solid gray color, and some bars would have a gradient yellow drawable?
You need custom bar BarChartRenderer to achieve this. I have provided a rough sample. Hope it helps.
Code for setting the barchart
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// custom colors that you want on top of the bars
ArrayList<Integer> myColors = new ArrayList<>();
myColors.add(Color.BLACK);
myColors.add(Color.YELLOW);
myColors.add(Color.BLUE);
myColors.add(Color.DKGRAY);
myColors.add(Color.GREEN);
myColors.add(Color.GRAY);
String[] myText = {"A Round", "B Round", "C Round", "D Round", "E Round", "F Round"};
BarChart mChart = (BarChart) findViewById(R.id.barChart);
mChart.setDrawBarShadow(false);
mChart.getDescription().setEnabled(false);
mChart.setDrawGridBackground(false);
XAxis xaxis = mChart.getXAxis();
xaxis.setDrawGridLines(false);
xaxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xaxis.setDrawLabels(true);
xaxis.setDrawAxisLine(false);
YAxis yAxisLeft = mChart.getAxisLeft();
yAxisLeft.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);
yAxisLeft.setDrawGridLines(false);
yAxisLeft.setDrawAxisLine(false);
yAxisLeft.setEnabled(false);
mChart.getAxisRight().setEnabled(false);
// set your custom renderer
mChart.setRenderer(new BarChartCustomRenderer(mChart, mChart.getAnimator(), mChart.getViewPortHandler(), myColors));
mChart.setDrawValueAboveBar(true);
Legend legend = mChart.getLegend();
legend.setEnabled(false);
ArrayList<BarEntry> valueSet1 = new ArrayList<BarEntry>();
for (int i = 0; i < 6; ++i) {
BarEntry entry = new BarEntry(i, (i + 1) * 10);
valueSet1.add(entry);
}
List<IBarDataSet> dataSets = new ArrayList<>();
BarDataSet barDataSet = new BarDataSet(valueSet1, " ");
barDataSet.setValueFormatter(new MyFormatter(myText));
barDataSet.setColor(Color.CYAN);
dataSets.add(barDataSet);
BarData data = new BarData(dataSets);
mChart.setData(data);
}
public class MyFormatter implements IValueFormatter {
String[] text;
public MyFormatter(String[] text) {
this.text = text;
}
#Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return String.valueOf((int)value)+"M" + ", " + text[(int) entry.getX()];
}
}
}
Custom Renderer
public class BarChartCustomRenderer extends BarChartRenderer {
private Paint myPaint;
private ArrayList<Integer> myColors;
public BarChartCustomRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler, ArrayList<Integer> myColors) {
super(chart, animator, viewPortHandler);
this.myPaint = new Paint();
this.myColors = myColors;
}
#Override
public void drawValues(Canvas c) {
super.drawValues(c);
// you can modify the original method
// so that everything is drawn on the canvas inside a single loop
// also you can add logic here to meet your requirements
int colorIndex = 0;
for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) {
BarBuffer buffer = mBarBuffers[i];
float left, right, top, bottom;
for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
myPaint.setColor(myColors.get(colorIndex++));
left = buffer.buffer[j];
right = buffer.buffer[j + 2];
top = buffer.buffer[j + 1];
bottom = buffer.buffer[j + 3];
// myPaint.setShader(new LinearGradient(left,top,right,bottom, Color.CYAN, myColors.get(colorIndex++), Shader.TileMode.MIRROR ));
c.drawRect(left, top, right, top+5f, myPaint);
}
}
}
#Override
public void drawValue(Canvas c, IValueFormatter formatter, float value, Entry entry, int dataSetIndex, float x, float y, int color) {
String text = formatter.getFormattedValue(value, entry, dataSetIndex, mViewPortHandler);
String[] splitText;
if(text.contains(",")){
splitText = text.split(",");
Paint paintStyleOne = new Paint(mValuePaint);
Paint paintStyleTwo = new Paint(mValuePaint);
paintStyleOne.setColor(Color.BLACK);
paintStyleTwo.setColor(Color.BLUE);
c.drawText(splitText[0], x, y-20f, paintStyleOne);
c.drawText(splitText[1], x, y, paintStyleTwo);
}
//else{
// super.drawValue(c, formatter, value, entry, dataSetIndex, x, y, color);
//}
}
}
RESULT
you can also do a gradient effect for the entire bar by slightly modifying the custom renderer :
myPaint.setShader(new LinearGradient(left,top,right,bottom, Color.CYAN, myColors.get(colorIndex++), Shader.TileMode.MIRROR ));
c.drawRect(left, top, right, bottom, myPaint);
you can similarly draw and style your text using the custom renderer.
Check this to learn more about custom renderers.
Update for using drawables instead of colors
//get bitmap from a drawable
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.myDrawable);
after than you can create a list of bitmaps and pass in onto the renderer instead of the list of colors.
if you want to draw on just the top of the bar you can use this :
c.drawBitmap(bitmap.get(index++), null, new RectF(left, top, right, top+5f), null);
or if you want to cover the entire bar, you can do so by using the bitmap like this:
c.drawBitmap(bitmap.get(index++), null, new RectF(left, top, right, bottom), null);

How can I draw a circle on highlight point in line chart?

I'm using mpchart to draw my charts.I wanted to increase the circle size of intersection point of highlighter and line dataset. How can I achieve this?
I read somewhere that we can add another dataset with highlighted point and increase its circle size. Is that really a good approach if my highlighter will be dragged back and forth and I'll have to update the new dataset very frequently?
When using the MpChart Library, the library contains a MarkerView class that helps us to insert the markers for displaying the selected value in the chart. We can use this MarkerView class to display any kind of view for the selected chart data.
So for the dot I created a new ChartMarker class and extended MarkerView class. Then in the constructor I passed the layout containing an image view with the dot as the src to the super.
public ChartMarker(Context context) {
//the super will take care of displaying the layout
super(context, R.layout.layout_dot);
}
Finally set the ChartMarker instance to the chart through chart.setMarkerView()
ChartMarker elevationMarker = new ChartMarker(getActivity());
elevationChart.setMarkerView(elevationMarker);
And for the layout_dot.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:background="#drawable/dot"
android:layout_width="5dp"
android:layout_height="5dp" />
</LinearLayout>
I find ultimate solution.another question only can draw one point of one dataset in linechart,if I have two dataset like below,it will not draw two point。
Below code is my solution:
(it should edit the library source code,so pls import as module)
1、in LineChartRenderer class
private final Paint mHighlightPointStokePaint = new Paint();
private final Paint mHighlightPointInnerPaint = new Paint();
#Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
LineData lineData = mChart.getLineData();
int entryIndex = -1;
for (Highlight high : indices) {
ILineDataSet set = lineData.getDataSetByIndex(high.getDataSetIndex());
if (set == null || !set.isHighlightEnabled())
continue;
Entry e = set.getEntryForXValue(high.getX(), high.getY());
if (!isInBoundsX(e, set))
continue;
entryIndex = set.getEntryIndex(e);
MPPointD pix = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(e.getX(), e.getY() * mAnimator
.getPhaseY());
high.setDraw((float) pix.x, (float) pix.y);
// draw the lines
drawHighlightLines(c, (float) pix.x, (float) pix.y, set);
}
if (entryIndex < 0) {
return;
}
if (mChart instanceof BarLineChartBase) {
BarLineChartBase chart = (BarLineChartBase) this.mChart;
if (chart.isDrawHighlightPoint()) {
mHighlightPointInnerPaint.reset();
mHighlightPointInnerPaint.setAntiAlias(true);
mHighlightPointInnerPaint.setStyle(Paint.Style.FILL);
//交点外圈白环
mHighlightPointStokePaint.reset();
mHighlightPointStokePaint.setAntiAlias(true);
mHighlightPointStokePaint.setStyle(Paint.Style.STROKE);
mHighlightPointStokePaint.setStrokeWidth(chart.getHighLightPointStrokeWidth());
mHighlightPointStokePaint.setColor(Color.WHITE);
List<ILineDataSet> dataSets = lineData.getDataSets();
for (ILineDataSet set : dataSets) {
if (entryIndex < set.getEntryCount()) {
Entry e = set.getEntryForIndex(entryIndex);
MPPointD pix = mChart.getTransformer(set.getAxisDependency())
.getPixelForValues(e.getX(), e.getY() * mAnimator.getPhaseY());
drawHighlightPoint(c, (float) pix.x, (float) pix.y, chart, set);
}
}
}
}
}
private void drawHighlightPoint(Canvas c, float x, float y, BarLineChartBase chart, ILineDataSet set) {
//点内圆的颜色和图表线条一致,且将颜色的不透明度调满!
mHighlightPointInnerPaint.setColor(((255) << 24) | set.getColor());
//绘制内圆
c.drawCircle(x, y, chart.getHighLightPointInnerRadius(), mHighlightPointInnerPaint);
//绘制外圆
c.drawCircle(x, y, chart.getHighLightPointInnerRadius(), mHighlightPointStokePaint);
}
2、in BarLineChartBase class
/**
* flag that indicates if draw highlight point is enabled or not
*/
protected boolean isDrawHighlightPoint = false;
/**
* the highlight point inner radius (px)
*/
protected int mHighLightPointInnerRadius = 1;
/**
* the highlight point stroke width (px)
*/
protected int mHighLightPointStrokeWidth = 1;
public boolean isDrawHighlightPoint() {
return isDrawHighlightPoint;
}
public void setDrawHighlightPoint(boolean drawHighlightPoint) {
isDrawHighlightPoint = drawHighlightPoint;
}
public int getHighLightPointInnerRadius() {
return mHighLightPointInnerRadius;
}
public void setHighLightPointInnerRadius(int mHighLightPointStrokeWidth) {
this.mHighLightPointInnerRadius = mHighLightPointStrokeWidth;
}
public int getHighLightPointStrokeWidth() {
return mHighLightPointStrokeWidth;
}
public void setHighLightPointStrokeWidth(int mHighLightPointStrokeWidth) {
this.mHighLightPointStrokeWidth = mHighLightPointStrokeWidth;
}
3、apply to your chart
lineChart.setDrawHighlightPoint(true);
lineChart.setHighLightPointInnerRadius(Utils.dp2px(5, context));
lineChart.setHighLightPointStrokeWidth(Utils.dp2px(3, context));

PDFJet-Open-source: How to set multiline text inside Cell object

I use PDFJet-Open-Source library to construct a pdf. So, I have couple of questions:
1) How can I place the multiline text inside Cell?
Problem description: Currently I faced with problem that I can't place the multiline text inside Cell object. I tried set text like "text1 \n text2..." but it does not have any effect. Unfortunatelly, open source version does not have TextColumn and Paragraph classes.
2) What is the CompositeTextLine and how to use it?
Problem description: Perhaps I have wrong imagination, but I tried to do the following:
...
CompositeTextLine ctl = new CompositTextLine(0,0);
ctl.addComponent(new TextLine(f1,"MyText1"));
ctl.addComponent(new TextLine(f1,"MyText2"));
ctl.addComponent(new TextLine(f1,"MyText3"));
Cell cell = new Cell(f1);
cell.setCompositeTextLine(ctl);
...
I expected to see several multiple lines in the Cell but I observed nothing. Moreover, if I add the line table.wrapAroundCellText(), I've got NullPointerException. If I call ctl.drawOn(page), I just observe: "MyText1 MyText2 MyText3" without line breaking.
UPDATE: I discovered the TextBox class, so that, if I write:
TextBox textbox = new TextBox(f1);
textbox.setText("First Line \n Second Line");
textbox.drawOn(page);
it will construct what I want:
First Line
Second Line
But still I am interested with the possibility of (1) and description of (2) and some of them variations, like to "How to set TextBox (or image etc.) inside Cell, not only single line?"
And last one, could anyone, please, refer me to the realization of "text justification" algorithm in Java or C++.
I can across the same problem, and I ended up extending Cell, and using WordUtils from apache commons lang3:
public class MultilineCell extends Cell {
private final int characterCount;
public MultilineCell(Font font, String content, int characterCount) {
super(font, content);
this.characterCount = characterCount;
}
#Override
public String getText() {
return WrapUtil.wrap(super.getText(), this.characterCount);
}
#Override
public float getHeight() {
float height = this.font.getBodyHeight();
String text = getText();
if (text != null) {
String[] wrappedTexts = text.split(System.getProperty("line.separator"));
if (wrappedTexts.length > 1) {
return (height * wrappedTexts.length) + this.top_padding + this.bottom_padding;
}
}
return height + this.top_padding + this.bottom_padding;
}
#Override
protected void paint(Page page, float x, float y, float w, float h) throws Exception {
page.setPenColor(this.getPenColor());
page.setPenWidth(this.lineWidth);
drawBorders(page, x, y, w, h);
drawText(page, x, y, w);
}
private void drawBorders(
Page page,
float x,
float y,
float cell_w,
float cell_h) throws Exception {
if (getBorder(Border.TOP) &&
getBorder(Border.BOTTOM) &&
getBorder(Border.LEFT) &&
getBorder(Border.RIGHT)) {
page.drawRect(x, y, cell_w, cell_h);
}
else {
if (getBorder(Border.TOP)) {
page.moveTo(x, y);
page.lineTo(x + cell_w, y);
page.strokePath();
}
if (getBorder(Border.BOTTOM)) {
page.moveTo(x, y + cell_h);
page.lineTo(x + cell_w, y + cell_h);
page.strokePath();
}
if (getBorder(Border.LEFT)) {
page.moveTo(x, y);
page.lineTo(x, y + cell_h);
page.strokePath();
}
if (getBorder(Border.RIGHT)) {
page.moveTo(x + cell_w, y);
page.lineTo(x + cell_w, y + cell_h);
page.strokePath();
}
}
}
private void drawText(
Page page,
float x,
float y,
float cell_w) throws IOException {
String wrappedText = WrapUtil.wrap(super.getText(), this.characterCount);
String[] lines = wrappedText.split(System.getProperty("line.separator"));
float x_text = x + this.left_padding;
float y_text = y + this.font.getAscent() + this.top_padding;
for (String line : lines) {
page.drawString(this.font, line, x_text, y_text);
y_text += this.font.getBodyHeight();
}
}
}
You can instantiate and add the MultilineCell as you would with a Cell:
List<Cell> rowCells = new ArrayList<Cell>();
rowCells.add(new MultilineCell(font, c.getString(reasonIdx), 42));
I know that extension is not a nice solution, and copying drawBorders() is even worse, but in this case, it is the only solution if you don't want to fork PDFJet.
This however breaks autoAdjustColumnWidths: the width is calculated on the whole text, instead of the longest line. So if you intend to use this method, either subclass Table, fork PDFJet, or extract just this method (the only downside of the latter is that I couldn't work around the cell padding though):
/**
* Auto adjusts the widths of all columns so that they are just wide enough to hold the text without truncation.
*/
private static void autoAdjustColumnWidths(List<List<Cell>> tableData) {
// Find the maximum text width for each column
float[] max_col_widths = new float[tableData.get(0).size()];
for (int i = 0; i < tableData.size(); i++) {
List<Cell> row = tableData.get(i);
for (int j = 0; j < row.size(); j++) {
Cell cell = row.get(j);
if (cell.getColSpan() == 1) {
float cellWidth = 0f;
if (cell.getImage() != null) {
cellWidth = cell.getImage().getWidth();
}
if (cell.getText() != null) {
// Is this a multiline cell? If so, measure the widest line
if (cell.getText().contains(MultilineCell.NEW_LINE)) {
String[] lines = cell.getText().split(MultilineCell.NEW_LINE);
for (String line : lines) {
if (cell.getFont().stringWidth(cell.getFallbackFont(), line) > cellWidth) {
cellWidth = cell.getFont().stringWidth(cell.getFallbackFont(), line);
}
}
}
// Standard (single-line) cell, measure whole text
else {
if (cell.getFont().stringWidth(cell.getFallbackFont(), cell.getText()) > cellWidth) {
cellWidth = cell.getFont().stringWidth(cell.getFallbackFont(), cell.getText());
}
}
}
cell.setWidth(cellWidth + 2f /*cell.left_padding*/ + 2f/*cell.right_padding*/);
if (max_col_widths[j] == 0f ||
cell.getWidth() > max_col_widths[j]) {
max_col_widths[j] = cell.getWidth();
}
}
}
}
for (int i = 0; i < tableData.size(); i++) {
List<Cell> row = tableData.get(i);
for (int j = 0; j < row.size(); j++) {
Cell cell = row.get(j);
cell.setWidth(max_col_widths[j]);
}
}
}
PDFJet is a funny library, btw: protected fields, every other method throws Exception, and the classes are definitely not designed for extension, even if there're not final.
There is a tricky solution for that:
As suggested before split your input into several lines (using for example using WordUtils from apache commons lang3)
String wrappedText = WrapUtil.wrap(super.getText(), this.characterCount);
String[] lines = wrappedText.split(System.getProperty("line.separator"));
After you have your lines, add as many rows as lines into your table with those separate lines.
To simulate "multiline cell" call function removeLineBetweenRows providing index of those added rows. It will look like a one big cell.

Categories

Resources