So I have am using the SwiftCharts graph, and have it setup like this.
func roundToTens(_ x : Double) -> Int {
return 10 * Int((x / 10.0).rounded(.awayFromZero))
}
func createChart(){
let chartConfig = BarsChartConfig(
valsAxisConfig: ChartAxisConfig(from: 0, to: Double(roundToTens(maximum)), by: Double(roundToTens(maximum)/6))
)
let frame = view2.bounds
let chart = BarsChart(
frame: frame,
chartConfig: chartConfig,
xTitle: "Categories",
yTitle: "Y axis",
bars: test,
color: UIColor.red,
barWidth: 20
)
self.chart = chart
view2.addSubview(chart.view)
self.view.addSubview(view2)
}
However, the graph ends up looking like this.
I tried fixing the bounds, by adding Margins to the bounds but all it did was make the graph look wrong. Is anyone familiar with the SwiftCharts framework and how I can centre the graph to the middle and make it overall smaller so that the top y-axis number is visible
Related
I wonder how this to be done . Please do not close this question I need suggestion or any hint to implement it.
Source https://www.youtube.com/watch?v=4uHyHRKmxZk
I have created A node which is SCNCylinder and drawn from target to the centre of the screen from updateAtTime method
Firstly I thought it was drawn with TextNode. SO I tried following
Inside that class I have method help to draw a node at each Unit I pass to UnitLength
func drawOtherBaseUnit(height:Float,intoThe unitType:UnitLength,toTheNode zAxisNode:SCNNode) {
print("---------------------------------------------------------------------")
let distanceInTarget = Converter.convert(value: Double(height), to: unitType).output + 1
print("DISTANCE",distanceInTarget)
print("---------------------------------------------------------------------")
var text = ""
switch unitType {
case .inches:
text = "INCH"
case .centimeters:
text = "CM"
default:
return
}
if !distanceInTarget.isNaN {
var distance = Int(distanceInTarget)
var i = 0
while distance > 0 {
print("distance ",distance)
let node = UnitNode(withPosition: SCNVector3Make(0, 0, 0), radius: CGFloat(Converter.convert(value: 0.1, from: unitType, to: UnitLength.meters).output),text:"\(text) \(i)",forType:unitType)
let valueIncreaseOnAsPerTarget = Int(distanceInTarget) - distance
let valueToIncreaseMeter = Converter.convert(value: Double(valueIncreaseOnAsPerTarget), from: unitType, to: UnitLength.meters).output
node.position.y = -Float(valueToIncreaseMeter)
node.position.x = 0
zAxisNode.addChildNode(node)
distance -= 1
i += 1
}
}
}
The UnitNode class is draw SCNText and some other nodes.
This is working fine. I can see node each provided unit.
But it if I use drawOtherBaseUnit for Inch as well as CM UI is Lagging it is not smooth.
Is it correct way to implement desired output ?
I'm very new dealing charts in swift. Using LineChartView to plot my data on graph. On zooming,values on Y-axis min and max change accordingly based on values of viewport min and max. For that implemented the following delegate.
func chartScaled(_ chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat) {
self.mapView.leftAxis.axisMaximum = axisY[Int(self.mapView.highestVisibleX)]
self.mapView.leftAxis.axisMinimum = axisY[Int(self.mapView.lowestVisibleX)]
}
Sometimes working fine, but after scaling the graph,the labels are not visible and graph is not scaling to normal position also.
Snippet for leftaxis
self.mapView!.leftAxis.enabled = true
self.mapView!.leftAxis.drawAxisLineEnabled = false
self.mapView!.leftAxis.spaceTop = 0.5
self.mapView!.leftAxis.spaceBottom = 0.4
self.mapView.leftAxis.granularity = 5.0
self.mapView.leftAxis.granularityEnabled = true
Please help me out where am doing wrong.
Thanks in advance.
I am pretty new to drawing programmatically. I've managed to draw a line graph, complete with points, lines, auto-scaling axes, and axis labels. In other screens, I can change characteristics of the graph and when I return to the screen, I refresh it with setNeedsDisplay() in the viewWillAppear function of the containing viewController. The lines are redrawn perfectly when I do this.
The new data that is added in other screens may require rescaling the axes. The problem is that when the graph is redrawn, the number labels on the axes are just added to the graph, without removing the old ones, meaning that some labels may be overwritten, while some old ones just remain there next to the new ones.
I think I see why this happens, in that I am creating a label and adding a subview, but not removing it. I guess I figured that since the lines are erased and redrawn, the labels would be, too. How do I cleanly relabel my axes? Is there a better way to do this? My function for creating the labels is listed below. This function is called by drawRect()
func createXAxisLabels(interval: Float, numIntervals: Int) {
let xstart: CGFloat = marginLeft
let yval: CGFloat = marginTop + graphHeight + 10 // 10 pts below the x-axis
var xLabelVals : [Float] = [0]
var xLabelLocs : [CGFloat] = [] // gives the locations for each label
for i in 0...numIntervals {
xLabelLocs.append(xstart + CGFloat(i) * graphWidth/CGFloat(numIntervals))
xLabelVals.append(Float(i) * interval)
}
if interval >= 60.0 {
xUnits = "Minutes"
xUnitDivider = 60
}
for i in 0...numIntervals {
let label = UILabel(frame: CGRectMake(0, 0, 50.0, 16.0))
label.center = CGPoint(x: xLabelLocs[i], y: yval)
label.textAlignment = NSTextAlignment.Center
if interval < 1.0 {
label.text = "\(Float(i) * interval)"
} else {
label.text = "\(i * Int(interval/xUnitDivider))"
}
label.font = UIFont.systemFontOfSize(14)
label.textColor = graphStructureColor
self.addSubview(label)
}
}
drawRect should just draw the rectangle, not change the view hierarchy. It can be called repeatedly. Those other labels are other views and do their own drawing.
You have a couple of options
Don't use labels -- instead just draw the text onto the rect.
-or-
Add/remove the labels in the view controller.
EDIT (from the comments): The OP provided their solution
I just replaced my code inside the for loop with:
let str : NSString = "\(xLabelVals[i])"
let paraAttrib = NSMutableParagraphStyle()
paraAttrib.alignment = NSTextAlignment.Center
let attributes = [
NSFontAttributeName: UIFont.systemFontOfSize(14),
NSForegroundColorAttributeName: UIColor.whiteColor(),
NSParagraphStyleAttributeName: paraAttrib]
let xLoc : CGFloat = CGFloat(xLabelLocs[i] - 25)
let yLoc : CGFloat = yval - 8.0
str.drawInRect(CGRectMake(xLoc, yLoc, 50, 16), withAttributes: attributes)
I'm using a PaintCode StyleKit to generate a bunch of gradients, but PaintCode exports them as a CGGradient. I wanted to add these gradients a layer. Is it possible to convert a CGGradient to a CAGradientLayer?
No. The point of a CAGradientLayer is that you get to describe to it the gradient you want and it draws the gradient for you. You are already past that step; you have already described the gradient you want (to PaintCode instead of to a CAGradientLayer) and thus you already have the gradient you want. Thus, it is silly for you even to want to use a CAGradientLayer, since if you were going to do that, why did you use PaintCode in the first place? Just draw the CGGradient, itself, into an image, a view, or even a layer.
You can't get the colors out of a CGGradient, but you can use the same values to set the CAGradientLayer's colors and locations properties. Perhaps it would help for you to modify the generated PCGradient class to keep the colors and locations around as NSArrays that you can pass into CAGradientLayer.
This can be important if you have a library of gradients and only occasionally need to use a gradient in one of the two formats.
Yes, it is possible, but requires some math to convert from a regular coordinate system (with x values from 0 to the width and y values from 0 to the height) to the coordinate system used by CAGradientLayer (with x values from 0 to 1 and y values from 0 to 1). And it requires some more math (quite complex) to get the slope right.
The distance from 0 to 1 for x will depend on the width of the original rectangle. And the distance from 0 to 1 for y will depend on the height of the original rectangle. So:
let convertedStartX = startX/Double(width)
let convertedStartY = startY/Double(height)
let convertedEndX = endX/Double(width)
let convertedEndY = endY/Double(height)
let intermediateStartPoint = CGPoint(x:convertedStartX,y:convertedStartY)
let intermediateEndPoint = CGPoint(x:convertedEndX,y:convertedEndY)
This works if your original rectangle was a square. If not, the slope of the line that defines the angle of the gradient will be wrong! To fix this see the excellent answer here: CAGradientLayer diagonal gradient
If you pick up the utility from there, then you can set your final converted start and end points as follows, starting with the already-adjusted point values from above:
let fixedStartEnd:(CGPoint,CGPoint) = LinearGradientFixer.fixPoints(start: intermediateStartPoint, end: intermediateEndPoint, bounds: CGSize(width:width,height:height))
let myGradientLayer = CAGradientLayer()
myGradientLayer.startPoint = fixedStartEnd.0
myGradientLayer.endPoint = fixedStartEnd.1
Here's code for a full Struct that you can use to store gradient data and get back CGGradients or CAGradientLayers as needed:
import UIKit
struct UniversalGradient {
//Doubles are more precise than CGFloats for the
//calculations needed to convert start and end
//to CAGradientLayers 1...0 format
var startX: Double
var startY: Double
var endX: Double
var endY: Double
let options: CGGradientDrawingOptions = [.drawsBeforeStartLocation, .drawsAfterEndLocation]
//for CAGradientLayer
var colors: [UIColor]
var locations: [Double]
//computed conversions
private var myCGColors: [CGColor] {
return self.colors .map {color in color.cgColor}
}
private var myCGFloatLocations: [CGFloat] {
return self.locations .map {location in CGFloat(location)}
}
//computed properties
var gradient: CGGradient {
return CGGradient(colorsSpace: nil, colors: myCGColors as CFArray, locations: myCGFloatLocations)!
}
var start: CGPoint {
return CGPoint(x: startX,y: startY)
}
var end: CGPoint {
return CGPoint(x: endX,y: endY)
}
//can't use computed property here
//since we need details of the specific environment's bounds to be passed in
//so this will be an instance function
func gradientLayer(width: CGFloat, height: CGFloat) -> CAGradientLayer {
//convert location x and y values from full coordinates to 0...1 for start and end
//this works great for position, but it gets the slope incorrect if the view is not square
//this is because the gradient is not drawn with the final scale
//it is drawn while the view is square, and then it gets stretched, changing the angle
//https://stackoverflow.com/questions/38821631/cagradientlayer-diagonal-gradient
let convertedStartX = startX/Double(width)
let convertedStartY = startY/Double(height)
let convertedEndX = endX/Double(width)
let convertedEndY = endY/Double(height)
let intermediateStartPoint = CGPoint(x:convertedStartX,y:convertedStartY)
let intermediateEndPoint = CGPoint(x:convertedEndX,y:convertedEndY)
let fixedStartEnd:(CGPoint,CGPoint) = LinearGradientFixer.fixPoints(start: intermediateStartPoint, end: intermediateEndPoint, bounds: CGSize(width:width,height:height))
let myGradientLayer = CAGradientLayer()
myGradientLayer.startPoint = fixedStartEnd.0
myGradientLayer.endPoint = fixedStartEnd.1
myGradientLayer.locations = self.locations .map {location in NSNumber(value: location)}
myGradientLayer.colors = self.colors .map {color in color.cgColor}
return myGradientLayer
}
}
I have created a CPTXYGraph using Core Plot in Swift. The graph works just fine. Now I am trying to add a legend. I have looked at several examples and come up with the following to create the legend:
var theLegend=CPTLegend(graph: graph)
var legendFill=CPTFill(color: CPTColor.blueColor())
theLegend.fill = legendFill
var legendLineStyle = CPTMutableLineStyle()
legendLineStyle.lineColor = CPTColor.whiteColor()
theLegend.borderLineStyle = legendLineStyle
theLegend.cornerRadius = 2.0
theLegend.swatchSize = CGSizeMake(15.0, 15.0)
graph.legend = theLegend
graph.legendAnchor = CPTRectAnchor.TopLeft
graph.legendDisplacement = CGPointMake(100.0, -100.0)
When I run this, I get a blue square with slightly rounded corners that appears to be about 10 X 10 pixels.
I tried setting the frame just to see if it would make a difference:
theLegend.frame = CGRectMake(0, 0, 200, 200)
This did not change anything.
Has anyone had any success with this in Swift?
Make sure all of your plots are already in the graph before using that legend initializer. You can also initialize a legend with an array of individual plots.
I found a solution here : Problems with legend in Core-Plot (pieChart)
You need to add this method
func legendTitleForPieChart(pieChart:CPTPieChart,recordIndex index:Int) -> NSString{
return "the title"
}
You need 3 things:
this delegate:
CPTPieChartDelegate
You need set the delegate to self on piechart:
pieChart.delegate = self
and feed the legend:
func legendTitleForPieChart(pieChart: CPTPieChart!, recordIndex idx: UInt) -> String! {
return "titulo"
}