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 #3533

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
5 changes: 2 additions & 3 deletions ChartsDemo-iOS/Swift/DemoBaseViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ enum Option {
case toggleAutoScaleMinMax
case toggleData
case toggleBarBorders
// LineChart
case toggleGradientLine
case toggleGradient
// CandleChart
case toggleShadowColorSameAsCandle
case toggleShowCandleBar
Expand Down Expand Up @@ -63,7 +62,7 @@ enum Option {
case .toggleData: return "Toggle Data"
case .toggleBarBorders: return "Toggle Bar Borders"
// LineChart
case .toggleGradientLine: return "Toggle Gradient Line"
case .toggleGradient: return "Toggle Gradient"
// CandleChart
case .toggleShadowColorSameAsCandle: return "Toggle shadow same color"
case .toggleShowCandleBar: return "Toggle show candle bar"
Expand Down
43 changes: 34 additions & 9 deletions ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class BarChartViewController: DemoBaseViewController {

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

var set1: BarChartDataSet! = nil
if let set = chartView.data?.first as? BarChartDataSet {
set1 = set
set1.values = yVals
set.values = yVals

setup(set)

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

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

setup(set)

Choose a reason for hiding this comment

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

I think setup(set) can go out of this if-else condition.


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
4 changes: 2 additions & 2 deletions ChartsDemo-iOS/Swift/Demos/LineChart1ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class LineChart1ViewController: DemoBaseViewController {
.toggleIcons,
.toggleStepped,
.toggleHighlight,
.toggleGradientLine,
.toggleGradient,
.animateX,
.animateY,
.animateXY,
Expand Down Expand Up @@ -194,7 +194,7 @@ class LineChart1ViewController: DemoBaseViewController {
set.mode = (set.mode == .cubicBezier) ? .horizontalBezier : .cubicBezier
}
chartView.setNeedsDisplay()
case .toggleGradientLine:
case .toggleGradient:
for set in chartView.data!.dataSets as! [LineChartDataSet] {
set.isDrawLineWithGradientEnabled = !set.isDrawLineWithGradientEnabled
setup(set)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,15 @@ open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, BarChartData
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/BarChartDataSetProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,12 @@ public protocol BarChartDataSetProtocol: BarLineScatterCandleBubbleChartDataSetP

/// 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 }
}
127 changes: 112 additions & 15 deletions Source/Charts/Renderers/BarChartRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,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])

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

context.saveGState()
defer { context.restoreGState() }

Expand Down Expand Up @@ -299,9 +296,109 @@ 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)
}
}

private func drawGradientBars(
context: CGContext,
dataSet: BarChartDataSetProtocol,
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.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
{
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)
}
}
}

private func drawDefaultBars(
context: CGContext,
dataSet: BarChartDataSetProtocol,
dateSetIndex: 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 All @@ -314,22 +411,22 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
for j in buffer.indices
{
let barRect = buffer[j]
guard viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width) else { continue }
guard viewPortHandler.isInBoundsRight(barRect.origin.x) else { break }

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

if !isSingleColor
{
// 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 All @@ -339,7 +436,7 @@ open class BarChartRenderer: BarLineScatterCandleBubbleRenderer
let element = createAccessibleElement(withIndex: j,
container: chart,
dataSet: dataSet,
dataSetIndex: index,
dataSetIndex: dateSetIndex,
stackSize: stackSize)
{ (element) in
element.accessibilityFrame = barRect
Expand Down
15 changes: 15 additions & 0 deletions Source/Charts/Utils/ChartUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
import Foundation
import CoreGraphics

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 {
if self > range.upperBound {
Expand Down Expand Up @@ -101,6 +108,14 @@ 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