Skip to content

View hierarchy and transforms

Ivan Schütz edited this page Nov 6, 2017 · 9 revisions

Pre-0.6 there would have been no need for this chapter - there was no zooming and panning and the chart contained only 1 view, where all the layers would render their content / add subviews.

With zooming and panning came the requirement to add new subviews (particularly in order to continue providing the same ways to add content to the chart as before), which would manage the transforms and required clipping.

Now a chart always contains the following 3 main views:

  1. Chart view: This is the view where the complete chart is displayed, it includes the content, the axes and external spacing (ChartSettings.leading, ChartSettings.top, ChartSettings.trailing and ChartSettings.bottom). This is the same view you pass to the chart when using the initializer that accepts one, or the one which is created automatically when passing a frame instead of a view. The default axis layers and guidelines draw their content directly on this view.

  2. Container view: The boundaries of this view are the inner frame of the chart (i.e. space between axes, where we add data points). It's a direct subview of chart view. The purpose of this view is to clip the content view (3.) and be the parent of chart points that have to be translated but not scaled during zooming, like lines, markers or labels.

  3. Content view: This view also exists for inner frame content. Initially it has the same frame as container view (2.) but can be scaled and translated. Content that is added as subview of this view will be scaled during zooming. Which makes sense e.g. for bars.

Here an image to make this clearer (the image doesn't show content view clipped but it will of course normally be).

viewsimg

It's important to be aware of these implementation details when working with transforms, as it may not be clear to which view the data views have to be added. In most cases each layer makes these decisions internally and you don't have to worry about it, except when using ChartPointsViewsLayer, which is handled in the following section.

ChartPointsViewsLayer transform modes

Being a very generic way to add content to the chart, this layer doesn't know what kind of views it creates (i.e. if they have to be scaled during transform, or only translated, or something else) and thus doesn't know where to add them. You have to help the layer a little by providing these informations. This can be done by passing a mode to its initializer, which is defined as follows:

public enum ChartPointsViewsLayerMode {
    case scaleAndTranslate, translate, custom
}

scaleAndTranslate

This mode will apply directly the chart transform to the view making is scale as well as translate during zomming and panning. This is recommended for views that scale well, e.g. rectangular bars. Internally this mode means that ChartPointsViewsLayer will add the views to the chart's content view.

translate

This mode will only translate the views during zooming and not change their size. This is recommended for text, markers, lines, etc. Internally this mode means that ChartPointsViewsLayer will add the views to the chart's container view.

custom

This mode is similar to translate, in that the view will not be scaled, i.e. also added to the container view. It expects you to also set a customTransformer function in ChartPointsViewsLayer, which will be called on each transform delta with the current state (e.g. the layer, from which you can use the axes to calculate the updated positions) and view, letting you decide how to move the view in reaction to the transform.

This mode makes sense, for example, when showing overlays or markers that have a specific offset of the point they originally represent. In the custom transform you would ensure that the view preserves this offset. Another use case for this is when you want to translate the views only along one axis and ignore the other, like in the 3 markers at the right side of this chart:

markerschart

The custom transform function for this chart would look like this:

chartPointsLayer.customTransformer = {(model, view, layer) -> Void in
    let updatedScreenLoc = layer.modelLocToScreenLoc(x: model.chartPoint.x.scalar, y: model.chartPoint.y.scalar)
    view.frame.origin = CGPoint(x: layer.chart?.containerView.frame.maxX ?? 0, y: updatedScreenLoc.y)
}

As you can see, only the y position is updated according to the current transform, while x stays fixed at the right border of the inner frame.

Axis locking

There are different modes to configure how to process the zooming an panning gesture, in relation to the axes. This can be set in ChartSettings.zoomPan:

max

This mode locks the transform to the axis with the max. delta. The chart will be zoomed/panned either along the x axis or y axis. This is probably what you want to use in most cases.

both

Zoom and pan of both axes will be derived directly from the gesture. Both can change at the same time. This is how zooming and panning usually works outside of charts, e.g. in images.

onlyX

Only x axis can be zoomed / panned.

onlyY

Only y axis can be zoomed / panned.

Making the chart scrollable

Sometimes you want to start the chart with a larger inner frame than the space available in the screen. This is currently only possible by setting an initial zoom level. When working with certain layers, like bars, you will have to do some calculations to set initial dimensions that are smaller than intended and achieve their intended size by being multiplied by the chart's initial zoom level.

A task to improve this can be found here.

Elastic zooming and panning (state: "alpha")

This can be set with ChartSettings.zoomPan. If true the chart will not stop zooming/panning at the respective limits, but continue normally and return to limits when the gesture stops. Implementation needs improvement though, sometimes it doesn't work correctly.