See Updates At Bottom (4/30/2015)
I'm implementing a Pie Chart in Swift for iOS using ios-charts, and have chosen to customize the legend. Of note, the chart is displayed within a cell of a UICollectionView. The problem is that on first display, the custom legend content is not being displayed. Instead, I get legend content generated from the data.
If I scroll the view off-screen, and then scroll it back onto the screen, the proper custom legend is displayed. So, I'm guessing that I need to force a redraw/relayout/re-something after setting my custom legend. I haven't figured out how to do that. Does anyone have an idea? Am I completely missing something? Thanks!
Chart on initial display - data-generated (wrong) legend
Chart after scrolling off and back onto the screen - (proper legend)
Here's my code for drawing this chart:
func initChart(pieChart: PieChartView) {
numFormatter.maximumFractionDigits = 0
pieChart.backgroundColor = UIColor.whiteColor()
pieChart.usePercentValuesEnabled = false
pieChart.drawHoleEnabled = true
pieChart.holeTransparent = true
pieChart.descriptionText = ""
pieChart.centerText = "30%\nComplete"
pieChart.data = getMyData()
// Setting custom legend info, called AFTER setting data
pieChart.legend.position = ChartLegend.ChartLegendPosition.LeftOfChartCenter
pieChart.legend.colors = [clrGreenDk, clrGold, clrBlue]
pieChart.legend.labels = ["Complete","Enrolled","Future"]
pieChart.legend.enabled = true
}
func getMyData() -> ChartData {
var xVals = ["Q201","R202","S203","T204","U205", "V206"]
var courses: [ChartDataEntry] = []
courses.append(ChartDataEntry(value: 3, xIndex: 0))
courses.append(ChartDataEntry(value: 3, xIndex: 1))
courses.append(ChartDataEntry(value: 4, xIndex: 2))
courses.append(ChartDataEntry(value: 4, xIndex: 3))
courses.append(ChartDataEntry(value: 3, xIndex: 4))
courses.append(ChartDataEntry(value: 3, xIndex: 5))
let dsColors = [clrGreenDk, clrGreenDk, clrBlue, clrBlue, clrGold, clrGold]
let pcds = PieChartDataSet(yVals: courses, label: "")
pcds.sliceSpace = CGFloat(4)
pcds.colors = dsColors
pcds.valueFont = labelFont!
pcds.valueFormatter = numFormatter
pcds.valueTextColor = UIColor.whiteColor()
return ChartData(xVals: xVals, dataSet: pcds)
}
Update 4/30/2015
Based on discussion with author of MPAndroidChart (on which ios-charts is based), it appears there is not a point in the chart display lifecycle where one can override the legend on "first draw". Basically, the chart is rendered when it is created, no matter what. If you set data on the chart, the chart uses that data to create the legend and then renders. It isn't possible to change the legend between the point of setting data, and the point of chart rendering.
setNeedsDisplay()
Potentially, one can wait for the chart to render, update the legend, and then call chart.setNeedsDisplay() to signal the chart to redraw. Sadly, there's a timing problem with this. If you call this method immediately after rendering the chart, it either doesn't fire or (more likely) it fires too soon and is effectively ignored. In my code, placing this call within viewDidLoad or viewDidAppear had no effect. However...
Building the same chart in Java for Android (using MPAndroidChart) results in the same issue. After messing around for a bit, I noted that if I called the chart.invalidate() after a delay (using Handler.postDelayed()), it would fire properly. It turns out a similar approach works for ios-charts on iOS.
If one uses GCD to delay the call to setNeedsDisplay(), for even a few milliseconds after the rendering, it seems to do the trick. I've added the following code immediately after initializing the chart's view in my ViewController ("cell" is the UICollectionViewCell containing the chart view):
delay(0.05) {
cell.pieChartView.legend.colors = [self.clrGreenDk, self.clrGold, self.clrBlue]
cell.pieChartView.legend.labels = ["Complete","Enrolled","Future"]
// Re-calc legend dimensions for proper position (Added 5/2/2015)
cell.pieChartView.legend.calculateDimensions(cell.pieChartView.labelFont!)
cell.pieChartView.setNeedsDisplay()
}
Using the awesome "delay" method from this SO post: https://stackoverflow.com/a/24318861
Obviously, this is a nasty hack, but it seems to do the trick. I'm not sure I like the idea of using this hack in production, though.
For any Android folk who stumble on this post:
The following code achieves the same effect using MPAndroidChart:
// Inside onCreate()
pie = (PieChart) findViewById(R.id.chart1);
configPieChart(pie);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
String[] legLabels = new String[]{"Complete","Enrolled","Future"};
ArrayList<Integer> legColors = new ArrayList<Integer>();
legColors.add(blue);
legColors.add(gold);
legColors.add(green);
pie.getLegend().setPosition(Legend.LegendPosition.LEFT_OF_CHART_CENTER);
pie.getLegend().setColors(legColors);
pie.getLegend().setLabels(legLabels);
pie.invalidate();
}
}, 20);
I am the author of ios-charts, and we're working on features for customizing the legend data, without those "hacks".
In the latest commits to ios-charts, you can already see extraLabels and extraColors properties that add extra lines to the legend, and a setLegend function that allows you to set a custom data entirely.
This will soon be added to the Android version as well.
Enjoy :-)
Related
The essence of the problem is that I want to write my own version of the AppBar that would include content as another Compose function. After looking at the source code of the current CollapsingTopAppBar implementation, I saw the following lines:
#Composable
private fun TwoRowsTopAppBar(
...
scrollBehavior: TopAppBarScrollBehavior?
) {
...
val pinnedHeightPx: Float = 64.dp
val maxHeightPx: Float = 152.dp
LocalDensity.current.run {
pinnedHeightPx = pinnedHeight.toPx()
maxHeightPx = maxHeight.toPx()
}
// Sets the app bar's height offset limit to hide just the bottom title area and keep top title
// visible when collapsed.
SideEffect {
if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
}
}
...
Surface(...) {
Column {
TopAppBarLayout(
...
heightPx = pinnedHeightPx
...
)
TopAppBarLayout(
...
heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
?: 0f),
...
)
}
}
}
As I understand it, scrollBehavior is used to handle the collapse and expansion behavior. In the current implementation, just constant values are put in heightOffsetLimit. And since I need my appbar implementation to be able to contain content of any size, I need to somehow know the size of this content in advance and put this value in heightOffsetLimit.
I have already written the code for my AppBar, so that it also contains content. But since I can't pass the height value of the content to scrollBehavior, the AppBar doesn't collapse to the end.
you need to calculate the height that the appbar will have before drawing it into the screen. I have followed this issue and solved my problem with the last solution. hope it helps:
Get height of element Jetpack Compose
use the content you can put (ex. an image or a huge text) as the MainContent
use your appbar as the DependentContent and use the size given in lambda to give the height to your appbar
finally set placeMainContent false as I believe you don't need to draw the image (or any other composable) directly in a box
and you will good to go
I have a HorizontalBarChart with mpAndroidChart and I am having problems to display the labels on the left side, but within the graph. It looks like this:
the labels are chopped of on the left side. This is done via the line
testchart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE)
On the right side, it works smoothly:
The labels are inside the graph and fully displayed. This is done by the line:
testchart.getXAxis().setPosition(XAxis.XAxisPosition.TOP_INSIDE)
Any idea what I am doing wrong?
My Code for the chart is:
BarData data = new BarData(new BarDataSet(entries, "Labeltest"));
data.setBarWidth(1); // set custom bar width
data.setDrawValues(false);
oBinding.testchart.setData(data);
oBinding.testchart.getXAxis().setLabelCount(labels.size());
oBinding.testchart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE);
oBinding.testchart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(labels));
// Hide grid lines
oBinding.testchart.getAxisLeft().setEnabled(false);
oBinding.testchart.getAxisRight().setEnabled(false);
// Hide graph description
oBinding.testchart.getDescription().setEnabled(false);
// Hide graph legend
oBinding.testchart.getLegend().setEnabled(false);
oBinding.testchart.invalidate(); // refresh
and in XML:
<com.github.mikephil.charting.charts.HorizontalBarChart
android:id="#+id/testchart"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
I have encountered the same problem, I solved it this way.
chart.getXAxis().setPosition(XAxisPosition.BOTTOM_INSIDE)
In that way the labels should be drawn over the bars. You can further reposition the labels by using the setXOffset(...) and setYOffset(...) methods of the XAxis class.
If you have a better way, please let me know, thank you
Try this:
XAxis xAxis = chart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM_INSIDE);
Added in response to comment from #andreas-zuercher:
This way has always worked for me. This also makes it easy to set other axis parameters like:
xAxis.setDrawGridLines(true);
xAxis.setGranularity(1f);
xAxis.setGranularityEnabled(true);
final String[] xTime = time.split(",");
xAxis.setValueFormatter(new IAxisValueFormatter() {
#Override
public String getFormattedValue(float value, AxisBase axis) {
return xTime[(int) value-1];
}
});
xAxis.setValueFormatter(new IndexAxisValueFormatter(xTime));
I am using OxyPlot to export plots.
When I export them, I want to add a footer to these plots with information like the path it is saved, a time-stamp, and so on...
Right now I am doing this by creating an extra X-axis on a different position-tier and then setting the sizes of all ticks and labels to zero except for the title font-size.
This works, but as you might imagine, this is quite hacky and does not look that good (as you cannot set for example the aligning).
So my question is, is there a possibility to add such a footer to the exported plot?
EDIT:
var xAxis = new OxyPlot.Axes.LinearAxis
{
Position = AxisPosition.Bottom,
PositionTier = 1,
Title = "Footer: i.e. path to my file",
MinorTickSize = 0.0,
MajorTickSize = 0.0,
FontSize = 0.0,
TitleFontSize = 12,
AxisDistance = 10
};
This is the workaround I mentioned.
I create an axis at position-tier 1, which is below the first one and then disable all visuals of it except the title.
And in the end I add it to my plotmodel pm.Axes.Add(xAxis).
To export my plotmodel I use PdfExporter like this:
using (var stream = File.Create(testFile.pdf))
{
PdfExporter.Export(pm, stream, 800, 500);
}
Greetings
Chriz
Just had to do the same thing with my project and thought I'd share how I managed it for anyone else in need of a footer.
I couldn't find any built in OxyPlot methods to add a header or footer but if you use OxyPlot.PDF it's built on top of PDFSharp and you have more options to customize your PDF export.
Remove any previous reference to OxyPlot.Pdf in your project.
Download OxyPlot.Pdf source code from: https://github.com/oxyplot/oxyplot
In your project in VS, right click your solution in 'Solution Explorer' and Add Existing Project.
Navigate to the downloaded source code and add OxyPlot.Pdf.csproj
Right click your project and Add Reference
Select 'Projects' on the left and check the box for OxyPlot.Pdf on the right. Hit OK.
Check that it's working by building and running project.
Go to PdfRenderContext.cs file and find the PdfRenderContext method near the top.
Add the code below then build and run your project.
This code creates a MigraDoc Document and then merges it with the OxyPlot PdfDocument.
public PdfRenderContext(double width, double height, OxyColor background)
{
//*** Original Code - Don't change **//
this.RendersToScreen = false;
this.doc = new PdfDocument();
var page = new PdfPage { Width = new XUnit(width), Height = new XUnit(height) };
this.doc.AddPage(page);
this.g = XGraphics.FromPdfPage(page);
if (background.IsVisible())
{
this.g.DrawRectangle(ToBrush(background), 0, 0, width, height);
}
//*** New Code to add footer **//
Document myDoc = new Document();
Section mySection = myDoc.AddSection();
Paragraph footerParagraph = mySection.Footers.Primary.AddParagraph();
footerParagraph.AddText(DateTime.Now.ToShortDateString());
footerParagraph.Format.Alignment = ParagraphAlignment.Right;
MigraDoc.Rendering.DocumentRenderer docRenderer = new MigraDoc.Rendering.DocumentRenderer(myDoc);
docRenderer.PrepareDocument();
docRenderer.RenderObject(g, XUnit.FromInch(9.5), XUnit.FromInch(8), "1in", footerParagraph);
}
When you export the PDF a date stamp is now added to the lower right corner of the PDF. Note that I was working with landscape 8.5x11 inch paper so you may need to change position if you don't see it on the plot. Upper left corner is 0,0. Once it's working, build the OxyPlot.Pdf project to create the dll and then you can add it as a reference to your project and remove the source code.
Result:
I'm trying to create functionality very similar to most websites these days.
The concept is 3 sections the size of the browser, the background images are supposed to be fixed positioned and revealed by the div scrolling up and down.
We need this to function as beautifully on mobile as it does on desktop, and it looks like Famous/angular is the solution.
Here is a pen.
http://codepen.io/LAzzam2/pen/XJrwbo
I'm using famous' Scroll.sync, firing javascript that positions the background image on every start / update / end.
scrollObject.sync.on("update", function (event) {
console.log('update');
test(event);
});
here is the function positioning the backgrounds.
function test(data){
var scroller = document.getElementsByClassName('famous-group');
styles = window.getComputedStyle(scroller[0], null);
tr = styles.getPropertyValue("-webkit-transform").replace('matrix(1, 0, 0, 1, 0,','').replace(')','');
var distanceTop = -(parseInt(tr));
var sections = document.getElementsByClassName('section');
sections[3].style.backgroundPosition="50% "+distanceTop+"px";
sections[4].style.backgroundPosition="50% "+(-(window.innerHeight)+distanceTop)+"px";
sections[5].style.backgroundPosition="50% "+(-(window.innerHeight*2)+distanceTop)+"px";
};
Any input / suggestions / advice would be wonderful, really just looking for a proof of concept with these 3 background images scrolling nicely.
That jittery-ness is unfortunate, I can't tell what would be causing the issue, except maybe the order in which events are fired?
**There are known issues, only works in -webkit browsers as of now
I think your idea to use Famous is good, but probably what I would do, would be taking a different approach to the problem.
You are solving this by touching the DOM, that is exactly what both Angular and Famous are meant to avoid.
If I had to face the same goal, I would probably use a Famous surface for the background instead of changing the property of the main one and synchronize its position with the scrolling view.
So, in your code, it would be something like this:
function test(data){
var scrollViewPosition = scrollObject.getAbsolutePosition();
var newBackgroundPosition = // Calculate the new background position
var newForegroundPosition = // Calculate the new foreground position
var backgroundSurface = backgroundSurface.position.set(newBackgroundPosition);
var foregroundSurface = foregroundSurface.position.set(newForegroundPosition);
};
I am programmatically drawing a flowchart using Java UNO Runtime Reference in which I want to display grid Lines permanently.Using following code I was able to display the Grid Lines but they are toggled ON and OFF alternately when the code executes.
XModel xModel = (XModel) UnoRuntime.queryInterface(XModel.class, xDrawDoc);
XController xController = xModel.getCurrentController();
XDispatchProvider xDisp = UnoRuntime.queryInterface(XDispatchProvider.class, xController);
XMultiComponentFactory xMCF = xContext.getServiceManager();
Object dispatchHelper = xMCF.createInstanceWithContext("com.sun.star.frame.DispatchHelper", xContext);
XDispatchHelper xDispatchHelper = UnoRuntime.queryInterface(XDispatchHelper.class, dispatchHelper);
PropertyValue[] navigatorDesc = new PropertyValue[1];
navigatorDesc[0] = new PropertyValue();
navigatorDesc[0].Name = "GridVisible";
navigatorDesc[0].Value = true;
xDispatchHelper.executeDispatch(xDisp, ".uno:GridVisible" , "", 0, navigatorDesc);
I want to permanently show Grid Lines.How can I achieve this using Java.Pls suggest. If there is any way of checking whether the Grid Lines are ON(any method which could return a boolean value), this could also be a useful approach.