So I upgraded my project, and pods, to work with Swift 2.0 and Xcode 7. But now my PieChart isn't working as expected anymore. My Pies are all starting from the 0 degree angle, which means that they are all beginning in the same place, above each other.
The data is added the same way as before and if I highlight them I can see that they are all there, but not originating from their respective places.
Code for adding my pieChartData:
var dataEntries: [ChartDataEntry] = []
var dataPoints: [String] = []
for var i = 0; i < expensesHistory.count; i++ {
let dataEntry = ChartDataEntry(value: expensesHistory[i].amount, xIndex: i)
dataEntries.append(dataEntry)
dataPoints.append(expensesHistory[i].category.rawValue)
}
let pieChartDataSet = PieChartDataSet(yVals: dataEntries)
let pieChartData = PieChartData(xVals: dataPoints, dataSet: pieChartDataSet)
pieChartView.data = pieChartData
var colors = [UIColor]()
colors.append(Colors.blueColor())
pieChartDataSet.colors = colors
pieChartDataSet.drawValuesEnabled = false
pieChartDataSet.sliceSpace = 4.0
The data gets loaded and added correctly, into different entries (there are 7) but all 7 pies are starting in the same place...so they don't result in a circle at all. Any ideas?
UPDATE #1
I translated the ChartsDemo to Swift as best I could and I end up with the same issue, but further debugging tells me that I'm probably only getting the last Entry pushed into the DataSet. If I set PieChartDataSet.drawValuesEnabled = true and PieChartView.legend.enabled = true I only see 1 value in the legend and 1 value for the DataSet. But I checked and my xVals and yVals contain more than 1 value, but it only displays one, and randomly chosen which one as well. I tried to find a pattern in which entry it chooses, but it just picks one at random.
I have added functionality to my PieChartView which allows me to press a button to highlight the entry associated with it, so that's why I thought that all the pies are placed above each other, but now I'm thinking it might be that the PieChartView is only showing 1 value, since I only get 1 value in the legend displayed, and not all of them on top of one another.
Here is my translation code from the ChartsDemo to Swift, keep in mind that I use Swift 2.0 (which is where this issue first presented itself).
private func setPieChartData() {
let mult = UInt32(100)
let count = Int(4)
let parties = [
"Party A", "Party B", "Party C", "Party D", "Party E", "Party F",
"Party G", "Party H", "Party I", "Party J", "Party K", "Party L",
"Party M", "Party N", "Party O", "Party P", "Party Q", "Party R",
"Party S", "Party T", "Party U", "Party V", "Party W", "Party X",
"Party Y", "Party Z"
]
var yVals1 = [ChartDataEntry]()
// IMPORTANT: In a PieChart, no values (Entry) should have the same xIndex (even if from different DataSets), since no values can be drawn above each other.
for var i = 0; i < count; i++
{
let chartDataEntry = ChartDataEntry(value: Double((arc4random_uniform(mult) + mult/5)), xIndex: i)
yVals1.append(chartDataEntry)
}
var xVals = [String?]()
for var i = 0; i < count; i++
{
xVals.append(parties[i % parties.count])
//[xVals addObject:parties[i % parties.count]]
}
let dataSet = PieChartDataSet(yVals: yVals1, label: "Election Results")
dataSet.sliceSpace = 3.0
// add a lot of colors
var testColors = [UIColor]()
testColors.append(Colors.blueColor())
let data = PieChartData(xVals: xVals, dataSet: dataSet)
pieChartView.data = data
dataSet.colors = testColors
dataSet.drawValuesEnabled = true
dataSet.sliceSpace = 4.0
}
Since this issue started when I updated to the Swift 2.0 compatible version of ios-charts I still think that this issue is connected to that update. I haven't touched this code in months prior to the update, and it works fine with the previous version. And as far as I can tell from a debugging point of view all my values are pushed correctly, but seems to not be handled correctly by the PieChartView.
UPDATE #2
Here is a sample project where I show you the issue I'm having. I did not include functionality for highlighting with buttons since it will take a bit more effort to get it working.
Link to sample project
How to:
You need Xcode 7 to run this.
You need CocoaPods installed.
Once you have installed CocoaPods use the terminal to go to the project folder and type "pod install" in root to install the Charts framework.
Use the ChartsIssue.xcworkspace to launch the project, NOT ChartsIssue.xcproj
I tried to simulate what your code do in ChartsDemo's pie chart view controller like below, but it does not have problem.
I looked at your code, it is also fine to me. I guess there might be something wrong with your data value, like class type? Anyway, based on current info, I cannot say what's wrong with it, but it seems like it's your project's problem.
There are two ways to move forward:
1.You double check the data value and try debug on your own, to figure out why;
2.Try to reproduce with ChartsDemo code, and post your data sample code, so I can reproduce it on my side; a screenshot is even better.
- (void)setDataCount:(int)count range:(double)range
{
double mult = range;
NSMutableArray *yVals1 = [[NSMutableArray alloc] init];
// IMPORTANT: In a PieChart, no values (Entry) should have the same xIndex (even if from different DataSets), since no values can be drawn above each other.
for (int i = 0; i < count; i++)
{
[yVals1 addObject:[[ChartDataEntry alloc] initWithValue:(arc4random_uniform(mult) + mult / 5) xIndex:i]];
}
NSMutableArray *xVals = [[NSMutableArray alloc] init];
for (int i = 0; i < count; i++)
{
[xVals addObject:parties[i % parties.count]];
}
PieChartDataSet *dataSet = [[PieChartDataSet alloc] initWithYVals:yVals1 label:#"Election Results"];
dataSet.sliceSpace = 3.0;
// add a lot of colors
NSMutableArray *colors = [[NSMutableArray alloc] init];
[colors addObject:[UIColor blueColor]];
PieChartData *data = [[PieChartData alloc] initWithXVals:xVals dataSet:dataSet];
_chartView.data = data;
dataSet.colors = colors;
dataSet.drawValuesEnabled = NO;
dataSet.sliceSpace = 4.0;
}
Update for question update 1&2:
I have download your demo project and manage to get it work on my side, I do see what you are saying, only one arc stripe while you should have 4 values;
I think you have hit an animation bug here or you mis-use it:
pieChartView.animate(xAxisDuration: 1.5, easingOption: ChartEasingOption.EaseOutBack)
pieChartView.animate(yAxisDuration: 1.5, easingOption: ChartEasingOption.EaseOutBack)
Simply comment one of them out and it will display correctly (not sure if the animation is fine), comment both will display correctly without animation too.
If you want animate both x and y axis, use below func also give you correct chart and animation:
pieChartView.animate(xAxisDuration: 1.5, yAxisDuration: 1.5, easingOption: ChartEasingOption.EaseOutBack)
I will post an issue to ask if using separate methods simultaneously is valid or not.
Related
I am trying to figure out a way to programmatically highlight or select a data point on a HighCharts graph.
So far I have not figured out how to pull a HIPoint out of a series, much less select that point. Also, if there is a way to get the tooltip hover to show up programmatically, rather than simply selecting the data point, that would be even better.
I have found many examples of how to do this in JavaScript, but the method of series[i].data[i].select() doesn't work in Swift, because data is [Any], and you can only set select on a HIPoint.
options.series[0].data[3].select() // doesn't work
(options.series[0].data[3] as! HIPoint).select() // crashes, because Any can't be converted into HIPoint
Data point on series[0] point[3] should be selected, or bring up the tooltip, but I can't figure out how to access the HIPoint to do anything with it at all.
You can add the HIPoint class object to the data:
let options = HIOptions()
let chart = HIChart()
chart.type = "line"
options.chart = chart
let title = HITitle()
title.text = "Demo chart"
options.title = title
let line = HILine()
let point = HIPoint()
point.y = 71.5
line.data = [29.9, point, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4]
options.series = [line]
And refer to the point in this way:
let point = self.chartView.options.series[0].data[1] as! HIPoint
point.select()
I've spent a few days trying to bind my data model to a lineseries. I works fine; however, I want to change the line color. I knew where to change the color, yet the chart and series would ignore my binding (was a SolidColorBrush). If I hard-coded the color in XAML it would work; however, if I tried to bind the same property to the color property in my view model it would not work. After too much time was spent I gave up for 2 reasons.
It just wouldn't work
I realized I was going to need to bind 'x'
number of view models to the chart to show more than one line series
at a time.
I eventually just defined my line series in the code behind like so...
LineSeries BuildLine(DosePointsViewModel model)
{
LineSeries series = new LineSeries();
// styles
Style poly = new Style(typeof(Polyline));
poly.Setters.Add(new Setter(Polyline.StrokeProperty, model.LineColor));
poly.Setters.Add(new Setter(Polyline.StrokeThicknessProperty, 3d));
series.PolylineStyle = poly;
Style pointStyle = new Style(typeof(LineDataPoint));
pointStyle.Setters.Add(new Setter(LineDataPoint.BackgroundProperty, model.LineColor));
series.DataPointStyle = pointStyle;
// binding
series.IsSelectionEnabled = false;
series.IndependentValueBinding = new System.Windows.Data.Binding("Distance");
series.DependentValueBinding = new System.Windows.Data.Binding("Dose");
// X axis
LinearAxis xAxis = new LinearAxis();
xAxis.Title = "Distance";
xAxis.ShowGridLines = false;
xAxis.Interval = 1;
xAxis.Orientation = AxisOrientation.X;
series.IndependentAxis = xAxis;
// Y axis
LinearAxis yAxis = new LinearAxis(); //series.DependentRangeAxis as LinearAxis;
yAxis.Maximum = 5000d;
yAxis.Minimum = -100d;
yAxis.Minimum = model.Points.Min(d => d.Dose) - model.Points.Min(d => d.Dose) * 0.50;
yAxis.Maximum = model.Points.Max(d => d.Dose) + model.Points.Max(d => d.Dose) * 0.05;
yAxis.ShowGridLines = true;
yAxis.Orientation = AxisOrientation.Y;
yAxis.Title = "Dose";
Style s = new Style(typeof(Line));
s.Setters.Add(new Setter(Line.StrokeProperty, new SolidColorBrush(Colors.LightBlue)));
s.Setters.Add(new Setter(Line.StrokeThicknessProperty, 1d));
yAxis.GridLineStyle = s;
series.DependentRangeAxis = yAxis;
return series;
}
Now, the color for my line series works. Of course, the primary reason for this is that I'm directly setting the color via ...
poly.Setters.Add(new Setter(Polyline.StrokeProperty, model.LineColor));
pointStyle.Setters.Add(new Setter(LineDataPoint.BackgroundProperty, model.LineColor));
So, my question is this. I want to be able to add multiple line series to the chart; however, when I try to do this, only the last item is being bound. Inside the code, this is done for each line series being created. Only the last line series is added to the chart.
DosePointsViewModel model = new DosePointsViewModel(_snc, m.Id);
LineSeries series = BuildLine(model);
DoseChart.Series.Clear();
DoseChart.Series.Add(series);
Wow, as I'm reading my question I realize that I am calling
DoseChart.Series.Clear();
Well that was an interesting find.
Im trying to make a graph like this:
https://www.google.com/finance?q=BCBA:PAMP
I have a line chart in chart.js, now I want to add labels (like the letters A, B, C) for certain dates.
Can't find a doc/example to start from. Any idea?
If its more simple to do with another library a recommendation is more than welcome.
Thanks!
Unfortunately, there is no native support in chart.js for what you are wanting. However, you can certainly add this capability using the plugin interface. This requires that you implement your own logic to draw the canvas pixels at the locations that you want them. It might sound challenging, but its easier than it sounds.
Here is an example plugin that will add a value above specific points in the chart (based upon configuration).
Chart.plugins.register({
afterDraw: function(chartInstance) {
if (chartInstance.config.options.showDatapoints || chartInstance.config.options.showDatapoints.display) {
var showOnly = chartInstance.config.options.showDatapoints.showOnly || [];
var helpers = Chart.helpers;
var ctx = chartInstance.chart.ctx;
var fontColor = helpers.getValueOrDefault(chartInstance.config.options.showDatapoints.fontColor, chartInstance.config.options.defaultFontColor);
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize + 5, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = fontColor;
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
if (showOnly.includes(dataset.data[i])) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
var scaleMax = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
var yPos = (scaleMax - model.y) / scaleMax >= 0.93 ? model.y + 20 : model.y - 5;
ctx.fillText(dataset.data[i], model.x, yPos);
}
}
});
}
}
});
It allows you to configure which points you want to annotate using this new configuration. The showOnly option contains the points that you want to label.
options: {
showDatapoints: {
display: true,
showOnly: [3, 10, 9]
},
}
Obviously, this only adds the datapoint value at the specified points, but you can just change the plugin to paint whatever you want to show instead. Simply replace ctx.fillText(dataset.data[i], model.x, yPos) with different code to render something different on the canvas.
Here is a codepen example to show you want it looks like.
Hello I want to create a dynamic chart that looks like this: phases in line chart
Is it possible with ChartJs to create vertical lines at certain milestones? (If possible also give each phase another color)
If not, what other libraries can handle dynamic line chart libraries that support phases?
Using Chart.js plugins can help you with this. They let you handle specific events that are triggered during the chart creation like beforeInit, afterUpdate or afterDraw and are also easy to implement :
var myPlugin = {
afterDraw: function(chart) {
// This will be triggered after the chart is drawn
}
};
Chart.pluginService.register(myPlugin);
A plugin doing what you are looking for will be linked below, but first let me explain how it works.
In your chart options, you'll need to add a new property, called phases, which is an array :
// The following is an example that won't work with every chart
options: {
scales: {
xAxes: [{
// `from` & `to` must exist in your xAxe ticks
phases: [{
from: "January",
to: "March",
// You can also define the color here
color: "blue"
},
{
from: "May",
to: "June",
color: "red"
}]
}]
}
}
And then, here is the plugin that use this information :
var phasePlugin = {
// You need to handle the `afterDraw` event since you draw phases edges
// after everything is drawn
afterDraw: function(chart)
{
// All the variables the plugin needs follow ...
var ctx = chart.chart.ctx;
var xAxeId = chart.config.options.scales.xAxes[0].id;
var xAxe = chart.scales[xAxeId];
var yAxeId = chart.config.options.scales.yAxes[0].id;
var yAxe = chart.scales[yAxeId];
var ticks = xAxe.ticks;
var phases = chart.config.options.scales.xAxes[0].phases;
// For every phase ...
for (var i = 0; i < phases.length; i++) {
// We check if the tick actually exists
if (ticks.indexOf(phases[i].from) == -1 || (ticks.indexOf(phases[i].to) == -1)) {
// Else, we skip to the next phase
continue;
}
// We get the x position of the first limit tick
var xPos = ((xAxe.width - xAxe.paddingRight) / xAxe.maxIndex) * ticks.indexOf(phases[i].from) + xAxe.left + 1;
// And draw the dashed line
ctx.setLineDash([8, 8]);
ctx.strokeStyle = phases[i].color;
ctx.strokeRect(xPos, yAxe.top, 0, yAxe.height);
// Same for the other limit
xPos = ((xAxe.width - xAxe.paddingRight) / xAxe.maxIndex) * ticks.indexOf(phases[i].to) + xAxe.left + 1;
ctx.strokeStyle = phases[i].color;
ctx.strokeRect(xPos, yAxe.top, 0, yAxe.height);
ctx.setLineDash([1,0]);
}
}
};
You can see a fully working example in this jsFiddle and here is its result :
How do we implement live tile in Unity3d Windows Phone??
I want the live tile show the current high score.
I have tried this:
#if UNITY_METRO
UnityEngine.WSA.Tile liveTile = Tile.main;
//then we update the tile with our latest high score
//the first three strings are for images (medium,wide,large)
//the last string is for text to display
//you can also pass in an XML file to describe the tile
liveTile.Update("","","", "Best round time: " + PlayerPrefs.GetInt("angka", 0));
#endif
I don't work for Prime 31. I attended the Unity 5 Roadshow and found out Prime 31 is giving away their Windows Phone and Store plug-ins for free (not sure for how long, they have a deal with Microsoft).
prime31.com
They do have support for titles in their Metro Essentials Plugin
Here is the code that is included in the demo...
if( GUILayout.Button( "Update Application Live Tile (standard)" ) )
{
// first, create the tile data
var tileData = new StandardTileData();
tileData.backContent = "I'm on the back";
tileData.backTitle = "BACK TITLE";
tileData.title = "Live Tile Title";
tileData.count = 12;
// now update the tile
Tiles.updateApplicationTile( tileData );
}
if( GUILayout.Button( "Create Live Tile (Flip)" ) )
{
// first, create the tile data
var tileData = new FlipTileData();
tileData.backContent = "Back of the Tile";
tileData.backBackgroundImage = "http://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Breastfeeding-icon-med.svg/202px-Breastfeeding-icon-med.svg.png";
tileData.backTitle = "Back Title Here";
tileData.backgroundImage = "http://cdn.memegenerator.net/instances/250x250/38333070.jpg";
tileData.smallBackgroundImage = "Assets/Tiles/FlipCycleTileSmall.png";
tileData.title = "Flip Tile Title";
tileData.wideBackBackgroundImage = "http://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-00-91-03-metablogapi/5775.WideTileAfter_5F00_49305C14.jpg";
tileData.wideBackContent = "Wide Back Content";
tileData.wideBackgroundImage = "Assets/Tiles/FlipCycleTileLarge.png";
tileData.count = 3;
// now update the tile
Tiles.createOrUpdateSecondaryLiveTile( "flippy-tile", tileData );
}
if( GUILayout.Button( "Create Live Tile (Iconic)" ) )
{
// first, create the tile data
var tileData = new IconicTileData();
tileData.iconImage = "http://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/Breastfeeding-icon-med.svg/202px-Breastfeeding-icon-med.svg.png";
tileData.backgroundColor = Prime31.WinPhoneEssentials.Color.colorFromARGB( 255, 40, 255, 40 );
tileData.smallIconImage = "http://cdn.memegenerator.net/instances/250x250/38333070.jpg";
tileData.wideContent1 = "Wide content 1";
tileData.wideContent2 = "Wide content 2";
tileData.wideContent3 = "Wide content 3";
tileData.title = "Live Tile Title";
tileData.count = 3;
// now update the tile
Tiles.createOrUpdateSecondaryLiveTile( "my-tile", tileData );
}