Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gradient bars for BarChart [master branch] #4411

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ChartsDemo-iOS/Swift/DemoBaseViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum Option {
case toggleAutoScaleMinMax
case toggleData
case toggleBarBorders
case toggleGradient
// CandleChart
case toggleShadowColorSameAsCandle
case toggleShowCandleBar
Expand Down Expand Up @@ -61,6 +62,8 @@ enum Option {
case .toggleAutoScaleMinMax: return "Toggle auto scale min/max"
case .toggleData: return "Toggle Data"
case .toggleBarBorders: return "Toggle Bar Borders"
// LineChart
case .toggleGradient: return "Toggle Gradient"
// CandleChart
case .toggleShadowColorSameAsCandle: return "Toggle shadow same color"
case .toggleShowCandleBar: return "Toggle show candle bar"
Expand Down
42 changes: 33 additions & 9 deletions ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class BarChartViewController: DemoBaseViewController {

self.options = [.toggleValues,
.toggleHighlight,
.toggleGradient,
.animateX,
.animateY,
.animateXY,
Expand Down Expand Up @@ -123,28 +124,51 @@ class BarChartViewController: DemoBaseViewController {
}
}

var set1: BarChartDataSet! = nil
if let set = chartView.data?.dataSets.first as? BarChartDataSet {
set1 = set
set1.replaceEntries(yVals)
setup(set)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's rename to a more meaningful name to imply it's about setup gradient

set.replaceEntries(yVals)
chartView.data?.notifyDataChanged()
chartView.notifyDataSetChanged()
} else {
set1 = BarChartDataSet(entries: yVals, label: "The year 2017")
set1.colors = ChartColorTemplates.material()
set1.drawValuesEnabled = false

let data = BarChartData(dataSet: set1)
let set = BarChartDataSet(entries: yVals, label: "The year 2017")
set.colors = ChartColorTemplates.material()
set.drawValuesEnabled = false

setup(set)

let data = BarChartData(dataSet: set)
data.setValueFont(UIFont(name: "HelveticaNeue-Light", size: 10)!)
data.barWidth = 0.9
chartView.data = data
}

// chartView.setNeedsDisplay()
}

private func setup(_ dataSet: BarChartDataSet) {
if dataSet.drawBarGradientEnabled {
dataSet.colors = [.black, .red, .white]
dataSet.gradientPositions = [0, 40, 100]
} else {
dataSet.colors = ChartColorTemplates.material()
dataSet.gradientPositions = nil
}
}

override func optionTapped(_ option: Option) {
super.handleOption(option, forChartView: chartView)
switch option {
case .toggleGradient:

chartView.data?.dataSets
.compactMap { $0 as? BarChartDataSet }
.forEach { (set) in
set.drawBarGradientEnabled = !set.drawBarGradientEnabled
setup(set)
}
chartView.setNeedsDisplay()
default:
super.handleOption(option, forChartView: chartView)
}
}

// MARK: - Actions
Expand Down
6 changes: 6 additions & 0 deletions ChartsDemo-iOS/Swift/Demos/LineChart1ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class LineChart1ViewController: DemoBaseViewController {
.toggleIcons,
.toggleStepped,
.toggleHighlight,
.toggleGradient,
.animateX,
.animateY,
.animateXY,
Expand Down Expand Up @@ -174,6 +175,11 @@ class LineChart1ViewController: DemoBaseViewController {
set.mode = (set.mode == .cubicBezier) ? .horizontalBezier : .cubicBezier
}
chartView.setNeedsDisplay()
case .toggleGradient:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible for you to add the objc portion? (feel free to say no)

for set in chartView.data!.dataSets as! [LineChartDataSet] {
set.mode = (set.mode == .cubicBezier) ? .horizontalBezier : .cubicBezier
}
chartView.setNeedsDisplay()

default:
super.handleOption(option, forChartView: chartView)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,15 @@ open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, IBarChartDat
open var stackLabels: [String] = []

// MARK: - Styling functions and accessors


open var drawBarGradientEnabled = false

open var gradientPositions: [CGFloat]?

open var gradientStart: CGPoint = .infinite

open var gradientEnd: CGPoint = .infinite

/// the color used for drawing the bar-shadows. The bar shadows is a surface behind the bar that indicates the maximum value
open var barShadowColor = NSUIColor(red: 215.0/255.0, green: 215.0/255.0, blue: 215.0/255.0, alpha: 1.0)

Expand Down
8 changes: 8 additions & 0 deletions Source/Charts/Data/Interfaces/IBarChartDataSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,12 @@ public protocol IBarChartDataSet: IBarLineScatterCandleBubbleChartDataSet

/// array of labels used to describe the different values of the stacked bars
var stackLabels: [String] { get set }

var drawBarGradientEnabled: Bool { get set }

var gradientPositions: [CGFloat]? { get set }

var gradientStart: CGPoint { get set }

var gradientEnd: CGPoint { get set }
}
114 changes: 103 additions & 11 deletions Source/Charts/Renderers/BarChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,11 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
guard let dataProvider = dataProvider else { return }

let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)

let valueToPixelMatrix = trans.valueToPixelMatrix

prepareBuffer(dataSet: dataSet, index: index)
trans.rectValuesToPixel(&_buffers[index].rects)

let borderWidth = dataSet.barBorderWidth
let borderColor = dataSet.barBorderColor
let drawBorder = borderWidth > 0.0

context.saveGState()

// draw the bar shadow before the values
Expand Down Expand Up @@ -399,9 +396,104 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
context.fill(barRect)
}
}


if dataSet.drawBarGradientEnabled
{
drawGradientBars(context: context, dataSet: dataSet, buffer: buffer, matrix: valueToPixelMatrix)
}
else
{
drawDefaultBars(context: context, dataSet: dataSet, dateSetIndex: index, buffer: buffer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe drawNormalBars or standard bars, etc? not sure which is better. @jjatie idea?

}
}

private func drawGradientBars(context: CGContext, dataSet: IBarChartDataSet, buffer: BarChartRenderer.Buffer, matrix: CGAffineTransform)
{

guard let gradientPositions = dataSet.gradientPositions else
{
assertionFailure("Must set `gradientPositions if `dataSet.drawBarGradientEnabled` is true")
return
}

guard let boundingBox = buffer.rects.union() else { return }
guard !boundingBox.isNull, !boundingBox.isInfinite, !boundingBox.isEmpty else { return }

let drawBorder = dataSet.barBorderWidth > 0

let gradientStart = dataSet.gradientEnd.isInfinite ?
CGPoint(x: boundingBox.minX, y: boundingBox.minY) :
dataSet.gradientEnd.applying(matrix)

let gradientEnd = dataSet.gradientStart.isInfinite ?
CGPoint(x: boundingBox.minX, y: boundingBox.maxY) :
dataSet.gradientStart.applying(matrix)

var gradientColorComponents: [CGFloat] = []
var gradientLocations: [CGFloat] = []

for position in gradientPositions.reversed()
{
let location = CGPoint(x: boundingBox.minX, y: position)
.applying(matrix)
let normalizedLocation =
(location.y - boundingBox.minY) / (boundingBox.maxY - boundingBox.minY)
switch normalizedLocation {
case ..<0:
gradientLocations.append(0)
case 0..<1:
gradientLocations.append(normalizedLocation)
case 1...:
gradientLocations.append(1)
default:
assertionFailure()
}
}

for color in dataSet.colors.reversed()
{
guard let (r, g, b, a) = color.nsuirgba else {
continue
}
gradientColorComponents += [r, g, b, a]
}

let baseColorSpace = CGColorSpaceCreateDeviceRGB()
guard let gradient = CGGradient(
colorSpace: baseColorSpace,
colorComponents: &gradientColorComponents,
locations: &gradientLocations,
count: gradientLocations.count) else {
return
}

for barRect in buffer.rects
{
context.saveGState()
defer { context.restoreGState() }

guard viewPortHandler.isInBoundsLeft(barRect.maxX) else { continue }
guard viewPortHandler.isInBoundsRight(barRect.minX) else { break }

context.beginPath()
context.addRect(barRect)
context.clip()
context.drawLinearGradient(gradient, start: gradientStart, end: gradientEnd, options: [])

if drawBorder
{
context.setStrokeColor(dataSet.barBorderColor.cgColor)
context.setLineWidth(dataSet.barBorderWidth)
context.stroke(barRect)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in gradient bars, accessibilityOrderedElements is missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liuxuan30 pushed commit. Hope that I did it correct.

}

private func drawDefaultBars(context: CGContext, dataSet: IBarChartDataSet, dateSetIndex index: Int, buffer: BarChartRenderer.Buffer)
{
let drawBorder = dataSet.barBorderWidth > 0
let isSingleColor = dataSet.colors.count == 1

if isSingleColor
{
context.setFillColor(dataSet.color(atIndex: 0).cgColor)
Expand Down Expand Up @@ -430,13 +522,13 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
// Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
context.setFillColor(dataSet.color(atIndex: j).cgColor)
}

context.fill(barRect)

if drawBorder
{
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.setStrokeColor(dataSet.barBorderColor.cgColor)
context.setLineWidth(dataSet.barBorderWidth)
context.stroke(barRect)
}

Expand Down
2 changes: 1 addition & 1 deletion Source/Charts/Renderers/LineChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ open class LineChartRenderer: LineRadarRenderer

return filled
}

open override func drawValues(context: CGContext)
{
guard
Expand Down
19 changes: 19 additions & 0 deletions Source/Charts/Utils/ChartUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ import CoreGraphics
import Cocoa
#endif

extension Array where Element == CGRect
{
func union() -> Element?
{
guard !isEmpty else { return nil }
return reduce(nil, { $0?.union($1) ?? $1 })
}
}

extension Comparable
{
func clamped(to range: ClosedRange<Self>) -> Self
Expand Down Expand Up @@ -116,6 +125,16 @@ extension Double

extension CGPoint
{
static var infinite: CGPoint
{
return CGPoint(x: CGFloat.infinity, y: CGFloat.infinity)
}

var isInfinite: Bool
{
return x.isInfinite || y.isInfinite
}

/// Calculates the position around a center point, depending on the distance from the center, and the angle of the position around the center.
func moving(distance: CGFloat, atAngle angle: CGFloat) -> CGPoint
{
Expand Down
36 changes: 36 additions & 0 deletions Source/Charts/Utils/Platform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ public typealias NSUIScrollView = UIScrollView
public typealias NSUIScreen = UIScreen
public typealias NSUIDisplayLink = CADisplayLink

extension NSUIColor
{
var nsuirgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)? {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move { to a new line

var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0

guard getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
return nil
}

return (red: red, green: green, blue: blue, alpha: alpha)
}
}

open class NSUIView: UIView
{
@objc var nsuiLayer: CALayer?
Expand Down Expand Up @@ -124,6 +140,26 @@ public class NSUIDisplayLink
}
}

extension NSUIColor
{
var nsuirgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)? {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though the imp is different, here are two var nsuirgba in one extension? typo? or two vars indeed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@larryonoff can you get the notification? I basically finished the review, jsut some minor issues to address.

var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0

guard let colorSpaceModel = cgColor.colorSpace?.model else {
return nil
}
guard colorSpaceModel == .rgb else {
return nil
}

getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return (red: red, green: green, blue: blue, alpha: alpha)
}
}

extension NSView
{
final var nsuiGestureRecognizers: [NSGestureRecognizer]?
Expand Down