Having issue with gradient color fill for balloon marker using context
- Dominant language
- Swift
- Stars
- 28k
- Forks
- 6k
- PR merge metrics
- No merged PRs in 30d
Description
import Foundation
import Charts
#if canImport(UIKit)
import UIKit
#endif
open class BalloonMarker: UIView, IMarker
{
/// The marker image to render
@objc open var image: NSUIImage? = nil
@objc open var offset: CGPoint = CGPoint()
@objc open weak var chartView: ChartViewBase? = nil
/// As long as size is 0.0/0.0 - it will default to the image's size
@objc open var size: CGSize = CGSize()
@objc open var arrowSize = CGSize(width: 15, height: 11)
@objc open var insets: UIEdgeInsets = .zero
@objc open var minimumSize = CGSize()
@objc open var fillColor: UIColor = .clear
@objc open var startColor: UIColor = .clear
@objc open var endColor: UIColor = .clear
@objc open var paragraphStyle: NSMutableParagraphStyle?
fileprivate var _attributedString: NSAttributedString?
fileprivate var _labelSize: CGSize = CGSize()
@objc public init(_ insets: UIEdgeInsets, _ fillColor: UIColor, _ startColor: UIColor, _ endColor: UIColor) {
super.init(frame: .zero)
self.insets = insets
self.startColor = startColor
self.endColor = endColor
self.fillColor = fillColor
paragraphStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle
paragraphStyle?.alignment = .center
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint {
var offset = self.offset
var size = self.size
if size.width == 0.0 && image != nil
{
size.width = image!.size.width
}
if size.height == 0.0 && image != nil
{
size.height = image!.size.height
}
let width = size.width
let height = size.height
let padding: CGFloat = 8.0
var origin = point
origin.x -= width / 2
origin.y -= height
if origin.x + offset.x < 0.0
{
offset.x = -origin.x + padding
}
else if let chart = chartView,
origin.x + width + offset.x > chart.bounds.size.width
{
offset.x = chart.bounds.size.width - origin.x - width - padding
}
if origin.y + offset.y < 0
{
offset.y = height + padding;
}
else if let chart = chartView,
origin.y + height + offset.y > chart.bounds.size.height
{
offset.y = chart.bounds.size.height - origin.y - height - padding
}
return offset
}
open func draw(context: CGContext, point: CGPoint) {
guard let attributedString = _attributedString else { return }
let offset = self.offsetForDrawing(atPoint: point)
let size = self.size
var rect = CGRect(
origin: CGPoint(
x: point.x + offset.x,
y: point.y + offset.y),
size: size)
rect.origin.x -= size.width / 2.0
rect.origin.y -= size.height
context.saveGState()
// use one of below options to fill gradient color
// Issue lies here
// context.setFillColor(fillColor.cgColor)
context.setFillColor(UIView.gradientColor(rect, startColor, endColor).cgColor)
if offset.y > 0 {
context.beginPath()
let rect2 = CGRect(x: rect.origin.x, y: rect.origin.y + arrowSize.height, width: rect.size.width, height: rect.size.height - arrowSize.height)
let bezierPath = UIBezierPath(roundedRect: rect2, cornerRadius: 5.0)
UIColor.clear.setStroke()
bezierPath.stroke()
let clipPath = bezierPath.cgPath
context.addPath(clipPath)
context.closePath()
context.fillPath()
// arraow vertex
context.beginPath()
let p1 = CGPoint(x: rect.origin.x + rect.size.width / 2.0 - arrowSize.width / 2.0, y: rect.origin.y + arrowSize.height + 1)
context.move(to: p1)
context.addLine(to: CGPoint(x: p1.x + arrowSize.width, y: p1.y))
context.addLine(to: CGPoint(x: point.x, y: point.y))
context.addLine(to: p1)
context.fillPath()
} else {
context.beginPath()
let rect2 = CGRect(x: rect.origin.x, y: rect.origin.y, width: rect.size.width, height: rect.size.height - arrowSize.height)
let bezierPath = UIBezierPath(roundedRect: rect2, cornerRadius: 5.0)
UIColor.clear.setStroke()
bezierPath.stroke()
let clipPath = bezierPath.cgPath
context.addPath(clipPath)
context.closePath()
context.fillPath()
// arraow vertex
context.beginPath()
let p1 = CGPoint(x: rect.origin.x + rect.size.width / 2.0 - arrowSize.width / 2.0, y: rect.origin.y + rect.size.height - arrowSize.height - 1)
context.move(to: p1)
context.addLine(to: CGPoint(x: p1.x + arrowSize.width, y: p1.y))
context.addLine(to: CGPoint(x: point.x, y: point.y))
context.addLine(to: p1)
context.fillPath()
}
if offset.y > 0 {
rect.origin.y += self.insets.top + arrowSize.height
} else {
rect.origin.y += self.insets.top
}
rect.size.height -= self.insets.top + self.insets.bottom
UIGraphicsPushContext(context)
attributedString.draw(in: rect)
UIGraphicsPopContext()
context.restoreGState()
}
open func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
_attributedString = NSAttributedString(string: String(entry.y))
if let _attributedString = _attributedString {
set(_attributedString)
}
}
@objc open func set(_ attributedString: NSAttributedString) {
_attributedString = attributedString
_labelSize = attributedString.size()
var size = CGSize()
size.width = _labelSize.width + self.insets.left + self.insets.right
size.height = _labelSize.height + self.insets.top + self.insets.bottom
size.width = max(minimumSize.width, size.width)
size.height = max(minimumSize.height, size.height)
self.size = size
}
}
extension UIView {
static func gradientColor(_ frame: CGRect, _ startColor: UIColor, _ endColor: UIColor) -> UIColor {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = frame
gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = image {
return UIColor(patternImage: image)
}
return .clear
}
}
import Foundation
import Charts
class DoubleLineMarker: BalloonMarker {
private let dateFormatter = DateFormatter()
var xAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 10)]
var yAttributes: [NSAttributedString.Key: Any] = [:]
override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
xAttributes[.paragraphStyle] = paragraphStyle
xAttributes[.backgroundColor] = UIColor.clear
yAttributes[.paragraphStyle] = paragraphStyle
yAttributes[.backgroundColor] = UIColor.clear
let x = stringForX(entry.x)
let y = stringForY(entry.y)
let firstAttributedString = NSMutableAttributedString(string: y, attributes: xAttributes)
let secondAttributedString = NSAttributedString(string: "\n")
let thirdAttributedString = NSAttributedString(string: x, attributes: yAttributes)
firstAttributedString.append(secondAttributedString)
firstAttributedString.append(thirdAttributedString)
set(firstAttributedString)
}
private func stringForX(_ x: Double) -> String {
dateFormatter.dateFormat = "d MMM"
dateFormatter.timeZone = .current
return "\(dateFormatter.string(from: Date(timeIntervalSince1970: x)))"
}
private func stringForY(_ y: Double) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let formattedNumber: String = numberFormatter.string(from: NSNumber(value: y)) ?? ""
return "\(formattedNumber)"
}
}
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
let currentColor: UIColor = .blue // pass gradient color as UIcolor
let startColor: UIColor = .black,
let endColor: UIColor = .white
let marker = DoubleLineMarker(UIEdgeInsets(top: 8, left: 8, bottom: 20, right: 8), currentColor, startColor, endColor)
marker.xAttributes = [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 12)]
marker.yAttributes = [.foregroundColor: UIColor.white, .font: UIFont.systemFont(ofSize: 10)]
marker.minimumSize = CGSize(width: 70, height: 35)
marker.chartView = chartView
chartView.marker = marker
}
Contributor guide
Assessment
This issue has not been assessed yet.