Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
417
iphone/Chart/Chart.xcodeproj/project.pbxproj
Normal file
417
iphone/Chart/Chart.xcodeproj/project.pbxproj
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
472644DE24400C0400B9C053 /* ExpandedTouchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472644DD24400C0300B9C053 /* ExpandedTouchView.swift */; };
|
||||
47375E3C2420E94E00FFCC49 /* ChartPresentationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E392420E94C00FFCC49 /* ChartPresentationData.swift */; };
|
||||
47375E3E2420E94E00FFCC49 /* ChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E3B2420E94E00FFCC49 /* ChartData.swift */; };
|
||||
47375E4B2420E97100FFCC49 /* ChartXAxisView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E3F2420E96C00FFCC49 /* ChartXAxisView.swift */; };
|
||||
47375E4C2420E97100FFCC49 /* ChartLineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E402420E96D00FFCC49 /* ChartLineView.swift */; };
|
||||
47375E4D2420E97100FFCC49 /* ChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E412420E96D00FFCC49 /* ChartView.swift */; };
|
||||
47375E4E2420E97100FFCC49 /* ChartInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E432420E96F00FFCC49 /* ChartInfoView.swift */; };
|
||||
47375E4F2420E97100FFCC49 /* ChartPointInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E442420E96F00FFCC49 /* ChartPointInfoView.swift */; };
|
||||
47375E502420E97100FFCC49 /* ChartPointIntersectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E452420E96F00FFCC49 /* ChartPointIntersectionView.swift */; };
|
||||
47375E512420E97100FFCC49 /* ChartMyPositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E462420E96F00FFCC49 /* ChartMyPositionView.swift */; };
|
||||
47375E522420E97100FFCC49 /* ChartYAxisView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E472420E96F00FFCC49 /* ChartYAxisView.swift */; };
|
||||
47375E542420E97100FFCC49 /* ChartPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47375E492420E97100FFCC49 /* ChartPreviewView.swift */; };
|
||||
47D48BD324302FE200FEFB1F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 47D48BD524302FE200FEFB1F /* Localizable.strings */; };
|
||||
ED46DE1E2D09B8A6007CACD6 /* ChartPathBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED46DE1C2D09B8A6007CACD6 /* ChartPathBuilder.swift */; };
|
||||
ED46DE1F2D09B8A6007CACD6 /* ChartPresentationLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED46DE1D2D09B8A6007CACD6 /* ChartPresentationLine.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
472644DD24400C0300B9C053 /* ExpandedTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandedTouchView.swift; sourceTree = "<group>"; };
|
||||
47375D962420D4DB00FFCC49 /* Chart.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Chart.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
47375D9A2420D4DB00FFCC49 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
47375E392420E94C00FFCC49 /* ChartPresentationData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartPresentationData.swift; sourceTree = "<group>"; };
|
||||
47375E3B2420E94E00FFCC49 /* ChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartData.swift; sourceTree = "<group>"; };
|
||||
47375E3F2420E96C00FFCC49 /* ChartXAxisView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartXAxisView.swift; sourceTree = "<group>"; };
|
||||
47375E402420E96D00FFCC49 /* ChartLineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartLineView.swift; sourceTree = "<group>"; };
|
||||
47375E412420E96D00FFCC49 /* ChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartView.swift; sourceTree = "<group>"; };
|
||||
47375E432420E96F00FFCC49 /* ChartInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartInfoView.swift; sourceTree = "<group>"; };
|
||||
47375E442420E96F00FFCC49 /* ChartPointInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartPointInfoView.swift; sourceTree = "<group>"; };
|
||||
47375E452420E96F00FFCC49 /* ChartPointIntersectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartPointIntersectionView.swift; sourceTree = "<group>"; };
|
||||
47375E462420E96F00FFCC49 /* ChartMyPositionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartMyPositionView.swift; sourceTree = "<group>"; };
|
||||
47375E472420E96F00FFCC49 /* ChartYAxisView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartYAxisView.swift; sourceTree = "<group>"; };
|
||||
47375E492420E97100FFCC49 /* ChartPreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartPreviewView.swift; sourceTree = "<group>"; };
|
||||
47D48BD6243030A200FEFB1F /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
47D48BD7243030B900FEFB1F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
47D48BD8243030C300FEFB1F /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
47D48BD9243030CB00FEFB1F /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BDA243030DF00FEFB1F /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BDB243030EB00FEFB1F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BDC243030F300FEFB1F /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BDD2430310000FEFB1F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BDE2430312400FEFB1F /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BDF2430312C00FEFB1F /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE02430313E00FEFB1F /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE1243031A900FEFB1F /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE2243031C700FEFB1F /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE3243031D300FEFB1F /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE4243031DC00FEFB1F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE5243031EE00FEFB1F /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE6243031F600FEFB1F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE72430320100FEFB1F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BE82430320F00FEFB1F /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BEA243032D800FEFB1F /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BEB243032DF00FEFB1F /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BEC243032E700FEFB1F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BED2430330B00FEFB1F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BEE2430332200FEFB1F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BEF2430332800FEFB1F /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BF02430333000FEFB1F /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BF12430333900FEFB1F /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BF22430334B00FEFB1F /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47D48BF32430335F00FEFB1F /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
ED46DE1C2D09B8A6007CACD6 /* ChartPathBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartPathBuilder.swift; sourceTree = "<group>"; };
|
||||
ED46DE1D2D09B8A6007CACD6 /* ChartPresentationLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartPresentationLine.swift; sourceTree = "<group>"; };
|
||||
FA4C8E53263B1FA80048FA99 /* common-release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "common-release.xcconfig"; path = "../../xcode/common-release.xcconfig"; sourceTree = "<group>"; };
|
||||
FA4C8E54263B1FA80048FA99 /* common-debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "common-debug.xcconfig"; path = "../../xcode/common-debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
47375D932420D4DB00FFCC49 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
47375D8C2420D4DB00FFCC49 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FA4C8E54263B1FA80048FA99 /* common-debug.xcconfig */,
|
||||
FA4C8E53263B1FA80048FA99 /* common-release.xcconfig */,
|
||||
47375D982420D4DB00FFCC49 /* Chart */,
|
||||
47375D972420D4DB00FFCC49 /* Products */,
|
||||
);
|
||||
indentWidth = 4;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 4;
|
||||
};
|
||||
47375D972420D4DB00FFCC49 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47375D962420D4DB00FFCC49 /* Chart.framework */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47375D982420D4DB00FFCC49 /* Chart */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47375DC22420D60200FFCC49 /* ChartData */,
|
||||
47375DC72420D60300FFCC49 /* Views */,
|
||||
47375D9A2420D4DB00FFCC49 /* Info.plist */,
|
||||
47D48BD524302FE200FEFB1F /* Localizable.strings */,
|
||||
);
|
||||
path = Chart;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47375DC22420D60200FFCC49 /* ChartData */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ED46DE1C2D09B8A6007CACD6 /* ChartPathBuilder.swift */,
|
||||
ED46DE1D2D09B8A6007CACD6 /* ChartPresentationLine.swift */,
|
||||
47375E3B2420E94E00FFCC49 /* ChartData.swift */,
|
||||
47375E392420E94C00FFCC49 /* ChartPresentationData.swift */,
|
||||
);
|
||||
path = ChartData;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47375DC72420D60300FFCC49 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47375E422420E96F00FFCC49 /* ChartInfo */,
|
||||
47375E412420E96D00FFCC49 /* ChartView.swift */,
|
||||
47375E402420E96D00FFCC49 /* ChartLineView.swift */,
|
||||
47375E492420E97100FFCC49 /* ChartPreviewView.swift */,
|
||||
47375E3F2420E96C00FFCC49 /* ChartXAxisView.swift */,
|
||||
47375E472420E96F00FFCC49 /* ChartYAxisView.swift */,
|
||||
472644DD24400C0300B9C053 /* ExpandedTouchView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
47375E422420E96F00FFCC49 /* ChartInfo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
47375E432420E96F00FFCC49 /* ChartInfoView.swift */,
|
||||
47375E442420E96F00FFCC49 /* ChartPointInfoView.swift */,
|
||||
47375E452420E96F00FFCC49 /* ChartPointIntersectionView.swift */,
|
||||
47375E462420E96F00FFCC49 /* ChartMyPositionView.swift */,
|
||||
);
|
||||
path = ChartInfo;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
47375D912420D4DB00FFCC49 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
47375D952420D4DB00FFCC49 /* Chart */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 47375D9E2420D4DB00FFCC49 /* Build configuration list for PBXNativeTarget "Chart" */;
|
||||
buildPhases = (
|
||||
47375D912420D4DB00FFCC49 /* Headers */,
|
||||
47375D922420D4DB00FFCC49 /* Sources */,
|
||||
47375D932420D4DB00FFCC49 /* Frameworks */,
|
||||
47375D942420D4DB00FFCC49 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Chart;
|
||||
productName = Chart;
|
||||
productReference = 47375D962420D4DB00FFCC49 /* Chart.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
47375D8D2420D4DB00FFCC49 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
DefaultBuildSystemTypeForWorkspace = Latest;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = CoMaps;
|
||||
TargetAttributes = {
|
||||
47375D952420D4DB00FFCC49 = {
|
||||
CreatedOnToolsVersion = 11.3;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 47375D902420D4DB00FFCC49 /* Build configuration list for PBXProject "Chart" */;
|
||||
compatibilityVersion = "Xcode 12.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
"en-GB",
|
||||
"zh-Hans",
|
||||
"zh-Hant",
|
||||
ar,
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
el,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
fi,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
ko,
|
||||
nb,
|
||||
nl,
|
||||
pl,
|
||||
pt,
|
||||
ro,
|
||||
ru,
|
||||
tr,
|
||||
th,
|
||||
sv,
|
||||
vi,
|
||||
sk,
|
||||
uk,
|
||||
);
|
||||
mainGroup = 47375D8C2420D4DB00FFCC49;
|
||||
productRefGroup = 47375D972420D4DB00FFCC49 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
47375D952420D4DB00FFCC49 /* Chart */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
47375D942420D4DB00FFCC49 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
47D48BD324302FE200FEFB1F /* Localizable.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
47375D922420D4DB00FFCC49 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
47375E512420E97100FFCC49 /* ChartMyPositionView.swift in Sources */,
|
||||
47375E3E2420E94E00FFCC49 /* ChartData.swift in Sources */,
|
||||
47375E4C2420E97100FFCC49 /* ChartLineView.swift in Sources */,
|
||||
ED46DE1E2D09B8A6007CACD6 /* ChartPathBuilder.swift in Sources */,
|
||||
ED46DE1F2D09B8A6007CACD6 /* ChartPresentationLine.swift in Sources */,
|
||||
47375E502420E97100FFCC49 /* ChartPointIntersectionView.swift in Sources */,
|
||||
47375E522420E97100FFCC49 /* ChartYAxisView.swift in Sources */,
|
||||
47375E4B2420E97100FFCC49 /* ChartXAxisView.swift in Sources */,
|
||||
47375E542420E97100FFCC49 /* ChartPreviewView.swift in Sources */,
|
||||
47375E3C2420E94E00FFCC49 /* ChartPresentationData.swift in Sources */,
|
||||
47375E4D2420E97100FFCC49 /* ChartView.swift in Sources */,
|
||||
472644DE24400C0400B9C053 /* ExpandedTouchView.swift in Sources */,
|
||||
47375E4F2420E97100FFCC49 /* ChartPointInfoView.swift in Sources */,
|
||||
47375E4E2420E97100FFCC49 /* ChartInfoView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
47D48BD524302FE200FEFB1F /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
47D48BD6243030A200FEFB1F /* en-GB */,
|
||||
47D48BD7243030B900FEFB1F /* zh-Hans */,
|
||||
47D48BD8243030C300FEFB1F /* zh-Hant */,
|
||||
47D48BD9243030CB00FEFB1F /* ar */,
|
||||
47D48BDA243030DF00FEFB1F /* cs */,
|
||||
47D48BDB243030EB00FEFB1F /* da */,
|
||||
47D48BDC243030F300FEFB1F /* de */,
|
||||
47D48BDD2430310000FEFB1F /* el */,
|
||||
47D48BDE2430312400FEFB1F /* es */,
|
||||
47D48BDF2430312C00FEFB1F /* fr */,
|
||||
47D48BE02430313E00FEFB1F /* ja */,
|
||||
47D48BE1243031A900FEFB1F /* fi */,
|
||||
47D48BE2243031C700FEFB1F /* hu */,
|
||||
47D48BE3243031D300FEFB1F /* id */,
|
||||
47D48BE4243031DC00FEFB1F /* it */,
|
||||
47D48BE5243031EE00FEFB1F /* ko */,
|
||||
47D48BE6243031F600FEFB1F /* nb */,
|
||||
47D48BE72430320100FEFB1F /* nl */,
|
||||
47D48BE82430320F00FEFB1F /* pl */,
|
||||
47D48BEA243032D800FEFB1F /* pt */,
|
||||
47D48BEB243032DF00FEFB1F /* ro */,
|
||||
47D48BEC243032E700FEFB1F /* ru */,
|
||||
47D48BED2430330B00FEFB1F /* en */,
|
||||
47D48BEE2430332200FEFB1F /* tr */,
|
||||
47D48BEF2430332800FEFB1F /* th */,
|
||||
47D48BF02430333000FEFB1F /* sv */,
|
||||
47D48BF12430333900FEFB1F /* vi */,
|
||||
47D48BF22430334B00FEFB1F /* sk */,
|
||||
47D48BF32430335F00FEFB1F /* uk */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
47375D9C2420D4DB00FFCC49 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FA4C8E54263B1FA80048FA99 /* common-debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
SDKROOT = iphoneos;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
47375D9D2420D4DB00FFCC49 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = FA4C8E53263B1FA80048FA99 /* common-release.xcconfig */;
|
||||
buildSettings = {
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
47375D9F2420D4DB00FFCC49 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Chart/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.comaps.chart;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
47375DA02420D4DB00FFCC49 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Chart/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.comaps.chart;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
47375D902420D4DB00FFCC49 /* Build configuration list for PBXProject "Chart" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
47375D9C2420D4DB00FFCC49 /* Debug */,
|
||||
47375D9D2420D4DB00FFCC49 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
47375D9E2420D4DB00FFCC49 /* Build configuration list for PBXNativeTarget "Chart" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
47375D9F2420D4DB00FFCC49 /* Debug */,
|
||||
47375DA02420D4DB00FFCC49 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 47375D8D2420D4DB00FFCC49 /* Project object */;
|
||||
}
|
||||
50
iphone/Chart/Chart/ChartData/ChartData.swift
Normal file
50
iphone/Chart/Chart/ChartData/ChartData.swift
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import UIKit
|
||||
|
||||
public enum ChartType {
|
||||
case regular
|
||||
case percentage
|
||||
}
|
||||
|
||||
public enum ChartLineType: String {
|
||||
case line
|
||||
case lineArea
|
||||
}
|
||||
|
||||
public protocol ChartFormatter {
|
||||
func xAxisString(from value: Double) -> String
|
||||
func yAxisString(from value: Double) -> String
|
||||
func yAxisLowerBound(from value: CGFloat) -> CGFloat
|
||||
func yAxisUpperBound(from value: CGFloat) -> CGFloat
|
||||
func yAxisSteps(lowerBound: CGFloat, upperBound: CGFloat) -> [CGFloat]
|
||||
}
|
||||
|
||||
public protocol ChartData {
|
||||
var xAxisValues: [Double] { get }
|
||||
var lines: [ChartLine] { get }
|
||||
var type: ChartType { get }
|
||||
}
|
||||
|
||||
public protocol ChartLine {
|
||||
var values: [ChartValue] { get }
|
||||
var color: UIColor { get }
|
||||
var type: ChartLineType { get }
|
||||
}
|
||||
|
||||
public struct ChartValue {
|
||||
let x: CGFloat
|
||||
let y: CGFloat
|
||||
|
||||
public init(xValues: CGFloat, y: CGFloat) {
|
||||
self.x = xValues
|
||||
self.y = y
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == ChartValue {
|
||||
var maxDistance: CGFloat { return map { $0.x }.max() ?? 0 }
|
||||
|
||||
func altitude(at relativeDistance: CGFloat) -> CGFloat {
|
||||
guard let distance = last?.x else { return 0 }
|
||||
return first { $0.x >= distance * relativeDistance }?.y ?? 0
|
||||
}
|
||||
}
|
||||
131
iphone/Chart/Chart/ChartData/ChartPathBuilder.swift
Normal file
131
iphone/Chart/Chart/ChartData/ChartPathBuilder.swift
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import UIKit
|
||||
|
||||
protocol ChartPathBuilder {
|
||||
func build(_ lines: [ChartPresentationLine])
|
||||
func makeLinePath(line: ChartPresentationLine) -> UIBezierPath
|
||||
func makePercentLinePath(line: ChartPresentationLine, bottomLine: ChartPresentationLine?) -> UIBezierPath
|
||||
}
|
||||
|
||||
extension ChartPathBuilder {
|
||||
func makeLinePreviewPath(line: ChartPresentationLine) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
let values = line.values
|
||||
let xScale = CGFloat(values.count) / values.maxDistance
|
||||
for i in 0..<values.count {
|
||||
let x = values[i].x * xScale
|
||||
let y = values[i].y - line.minY
|
||||
if i == 0 {
|
||||
path.move(to: CGPoint(x: x, y: y))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func makeLinePath(line: ChartPresentationLine) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
let values = line.values
|
||||
let xScale = CGFloat(values.count) / values.maxDistance
|
||||
for i in 0..<values.count {
|
||||
let x = values[i].x * xScale
|
||||
let y = values[i].y - line.minY
|
||||
if i == 0 {
|
||||
path.move(to: CGPoint(x: x, y: y))
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func makePercentLinePreviewPath(line: ChartPresentationLine, bottomLine: ChartPresentationLine?) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: 0, y: 0))
|
||||
let aggregatedValues = line.aggregatedValues
|
||||
let xScale = CGFloat(aggregatedValues.count) / aggregatedValues.maxDistance
|
||||
for i in 0..<aggregatedValues.count {
|
||||
let x = aggregatedValues[i].x * xScale
|
||||
let y = aggregatedValues[i].y - CGFloat(line.minY)
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
path.addLine(to: CGPoint(x: aggregatedValues.maxDistance, y: 0))
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
|
||||
func makePercentLinePath(line: ChartPresentationLine, bottomLine: ChartPresentationLine?) -> UIBezierPath {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: 0, y: 0))
|
||||
let aggregatedValues = line.aggregatedValues
|
||||
let xScale = CGFloat(aggregatedValues.count) / aggregatedValues.maxDistance
|
||||
for i in 0..<aggregatedValues.count {
|
||||
let x = aggregatedValues[i].x * xScale
|
||||
let y = aggregatedValues[i].y - CGFloat(line.minY)
|
||||
path.addLine(to: CGPoint(x: x, y: y))
|
||||
}
|
||||
path.addLine(to: CGPoint(x: aggregatedValues.maxDistance, y: 0))
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
final class DefaultChartPathBuilder {
|
||||
private let builders: [ChartType: ChartPathBuilder] = [
|
||||
.regular : LinePathBuilder(),
|
||||
.percentage : PercentagePathBuilder()
|
||||
]
|
||||
|
||||
func build(_ lines: [ChartPresentationLine], type: ChartType) {
|
||||
builders[type]?.build(lines)
|
||||
}
|
||||
}
|
||||
|
||||
final class LinePathBuilder: ChartPathBuilder {
|
||||
func build(_ lines: [ChartPresentationLine]) {
|
||||
lines.forEach {
|
||||
$0.aggregatedValues = $0.values
|
||||
if $0.type == .lineArea {
|
||||
$0.minY = 0
|
||||
for val in $0.values {
|
||||
$0.maxY = max(val.y, $0.maxY)
|
||||
}
|
||||
$0.path = makePercentLinePath(line: $0, bottomLine: nil)
|
||||
$0.previewPath = makePercentLinePreviewPath(line: $0, bottomLine: nil)
|
||||
} else {
|
||||
for val in $0.values {
|
||||
$0.minY = min(val.y, $0.minY)
|
||||
$0.maxY = max(val.y, $0.maxY)
|
||||
}
|
||||
$0.path = makeLinePath(line: $0)
|
||||
$0.previewPath = makeLinePreviewPath(line: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class PercentagePathBuilder: ChartPathBuilder {
|
||||
func build(_ lines: [ChartPresentationLine]) {
|
||||
lines.forEach {
|
||||
$0.minY = 0
|
||||
$0.maxY = CGFloat(Int.min)
|
||||
}
|
||||
|
||||
for i in 0..<lines[0].values.count {
|
||||
let sum = CGFloat(lines.reduce(0) { (r, l) in r + l.values[i].y })
|
||||
var aggrPercentage: CGFloat = 0
|
||||
lines.forEach {
|
||||
aggrPercentage += CGFloat($0.values[i].y) / sum * 100
|
||||
$0.aggregatedValues.append(ChartValue(xValues: lines[0].values[i].x, y: aggrPercentage))
|
||||
$0.maxY = max(round(aggrPercentage), CGFloat($0.maxY))
|
||||
}
|
||||
}
|
||||
|
||||
var prevLine: ChartPresentationLine? = nil
|
||||
lines.forEach {
|
||||
$0.path = makePercentLinePath(line: $0, bottomLine: prevLine)
|
||||
$0.previewPath = makePercentLinePreviewPath(line: $0, bottomLine: prevLine)
|
||||
prevLine = $0
|
||||
}
|
||||
}
|
||||
}
|
||||
56
iphone/Chart/Chart/ChartData/ChartPresentationData.swift
Normal file
56
iphone/Chart/Chart/ChartData/ChartPresentationData.swift
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import UIKit
|
||||
|
||||
public class ChartPresentationData {
|
||||
private let chartData: ChartData
|
||||
private var presentationLines: [ChartPresentationLine]
|
||||
private let pathBuilder = DefaultChartPathBuilder()
|
||||
|
||||
let formatter: ChartFormatter
|
||||
var linesCount: Int { chartData.lines.count }
|
||||
var pointsCount: Int { chartData.xAxisValues.count }
|
||||
var xAxisValues: [Double] { chartData.xAxisValues }
|
||||
var type: ChartType { chartData.type }
|
||||
var labels: [String]
|
||||
var lower = CGFloat(Int.max)
|
||||
var upper = CGFloat(Int.min)
|
||||
|
||||
public init(_ chartData: ChartData, formatter: ChartFormatter) {
|
||||
self.chartData = chartData
|
||||
self.formatter = formatter
|
||||
self.presentationLines = chartData.lines.map { ChartPresentationLine($0) }
|
||||
self.labels = chartData.xAxisValues.map { formatter.xAxisString(from: $0) }
|
||||
recalculateBounds()
|
||||
}
|
||||
|
||||
func labelAt(_ point: CGFloat) -> String {
|
||||
formatter.xAxisString(from: xAxisValueAt(point))
|
||||
}
|
||||
|
||||
func xAxisValueAt(_ point: CGFloat) -> Double {
|
||||
let distance = chartData.xAxisValues.last!
|
||||
let p1 = floor(point)
|
||||
let p2 = ceil(point)
|
||||
let v1 = p1 / CGFloat(pointsCount) * distance
|
||||
let v2 = p2 / CGFloat(pointsCount) * distance
|
||||
return v1 + (v2 - v1) * Double(point.truncatingRemainder(dividingBy: 1))
|
||||
}
|
||||
|
||||
func lineAt(_ index: Int) -> ChartPresentationLine {
|
||||
presentationLines[index]
|
||||
}
|
||||
|
||||
private func recalculateBounds() {
|
||||
presentationLines.forEach { $0.aggregatedValues = [] }
|
||||
pathBuilder.build(presentationLines, type: type)
|
||||
|
||||
var l = CGFloat(Int.max)
|
||||
var u = CGFloat(Int.min)
|
||||
presentationLines.forEach {
|
||||
l = min($0.minY, l)
|
||||
u = max($0.maxY, u)
|
||||
}
|
||||
lower = l
|
||||
upper = u
|
||||
}
|
||||
}
|
||||
|
||||
19
iphone/Chart/Chart/ChartData/ChartPresentationLine.swift
Normal file
19
iphone/Chart/Chart/ChartData/ChartPresentationLine.swift
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import UIKit
|
||||
|
||||
final class ChartPresentationLine {
|
||||
private let chartLine: ChartLine
|
||||
|
||||
var aggregatedValues: [ChartValue] = []
|
||||
var minY: CGFloat = CGFloat(Int.max)
|
||||
var maxY: CGFloat = CGFloat(Int.min)
|
||||
var path = UIBezierPath()
|
||||
var previewPath = UIBezierPath()
|
||||
|
||||
var values: [ChartValue] { chartLine.values }
|
||||
var color: UIColor { chartLine.color }
|
||||
var type: ChartLineType { chartLine.type }
|
||||
|
||||
init(_ chartLine: ChartLine) {
|
||||
self.chartLine = chartLine
|
||||
}
|
||||
}
|
||||
22
iphone/Chart/Chart/Info.plist
Normal file
22
iphone/Chart/Chart/Info.plist
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
197
iphone/Chart/Chart/Views/ChartInfo/ChartInfoView.swift
Normal file
197
iphone/Chart/Chart/Views/ChartInfo/ChartInfoView.swift
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import UIKit
|
||||
|
||||
struct ChartLineInfo {
|
||||
let color: UIColor
|
||||
let point: CGPoint
|
||||
let formattedValue: String
|
||||
}
|
||||
|
||||
protocol ChartInfoViewDelegate: AnyObject {
|
||||
func chartInfoView(_ view: ChartInfoView, infoAtPointX pointX: CGFloat) -> (String, [ChartLineInfo])?
|
||||
func chartInfoView(_ view: ChartInfoView, didCaptureInfoView captured: Bool)
|
||||
func chartInfoView(_ view: ChartInfoView, didMoveToPoint pointX: CGFloat)
|
||||
}
|
||||
|
||||
class ChartInfoView: ExpandedTouchView {
|
||||
weak var delegate: ChartInfoViewDelegate?
|
||||
|
||||
private let pointInfoView = ChartPointInfoView()
|
||||
private let pointsView = ChartPointIntersectionView(frame: CGRect(x: 0, y: 0, width: 2, height: 0))
|
||||
private let myPositionView = ChartMyPositionView(frame: CGRect(x: 0, y: 0, width: 2, height: 0))
|
||||
private var lineInfo: ChartLineInfo?
|
||||
|
||||
fileprivate var captured = false {
|
||||
didSet {
|
||||
delegate?.chartInfoView(self, didCaptureInfoView: captured)
|
||||
}
|
||||
}
|
||||
|
||||
private var _infoX: CGFloat = 0
|
||||
var infoX: CGFloat {
|
||||
get { _infoX }
|
||||
set {
|
||||
_infoX = newValue
|
||||
update(bounds.width * _infoX)
|
||||
}
|
||||
}
|
||||
|
||||
var myPositionX: CGFloat = -1 {
|
||||
didSet {
|
||||
if myPositionX < 0 || myPositionX > 1 {
|
||||
myPositionView.isHidden = true
|
||||
return
|
||||
}
|
||||
myPositionView.isHidden = false
|
||||
updateMyPosition()
|
||||
}
|
||||
}
|
||||
|
||||
var tooltipBackgroundColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
pointInfoView.backgroundColor = tooltipBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
var font: UIFont = UIFont.systemFont(ofSize: 12, weight: .regular) {
|
||||
didSet {
|
||||
pointInfoView.font = font
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor = UIColor.black {
|
||||
didSet {
|
||||
pointInfoView.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
public var infoBackgroundColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
pointInfoView.backgroundColor = infoBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
public var infoShadowColor: UIColor = UIColor.black {
|
||||
didSet {
|
||||
pointInfoView.layer.shadowColor = infoShadowColor.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
public var infoShadowOpacity: Float = 0.25 {
|
||||
didSet {
|
||||
pointInfoView.layer.shadowOpacity = infoShadowOpacity
|
||||
}
|
||||
}
|
||||
|
||||
var panGR: UIPanGestureRecognizer!
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
addSubview(myPositionView)
|
||||
myPositionView.isHidden = true
|
||||
addSubview(pointsView)
|
||||
addSubview(pointInfoView)
|
||||
isExclusiveTouch = true
|
||||
panGR = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:)))
|
||||
panGR.delegate = self
|
||||
addGestureRecognizer(panGR)
|
||||
pointInfoView.textColor = textColor
|
||||
pointInfoView.backgroundColor = tooltipBackgroundColor
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func update(_ x: CGFloat? = nil) {
|
||||
guard bounds.width > 0 else { return }
|
||||
let x = x ?? pointsView.center.x
|
||||
_infoX = x / bounds.width
|
||||
guard let delegate = delegate,
|
||||
let (label, intersectionPoints) = delegate.chartInfoView(self, infoAtPointX: x) else { return }
|
||||
lineInfo = intersectionPoints[0]
|
||||
pointsView.updatePoint(lineInfo!)
|
||||
pointInfoView.update(x: x, label: label, points: intersectionPoints)
|
||||
updateViews(point: lineInfo!.point)
|
||||
}
|
||||
|
||||
private func updateMyPosition() {
|
||||
myPositionView.center = CGPoint(x: bounds.width * myPositionX, y: myPositionView.center.y)
|
||||
guard let (_, myPositionPoints) = delegate?.chartInfoView(self, infoAtPointX: myPositionView.center.x) else { return }
|
||||
myPositionView.pinY = myPositionPoints[0].point.y
|
||||
}
|
||||
|
||||
@objc func onPan(_ sender: UIPanGestureRecognizer) {
|
||||
let x = sender.location(in: self).x
|
||||
switch sender.state {
|
||||
case .possible:
|
||||
break
|
||||
case .began:
|
||||
guard let lineInfo = lineInfo else { return }
|
||||
captured = abs(x - lineInfo.point.x) <= 22
|
||||
case .changed:
|
||||
if captured {
|
||||
if x < bounds.minX || x > bounds.maxX {
|
||||
return
|
||||
}
|
||||
update(x)
|
||||
delegate?.chartInfoView(self, didMoveToPoint: x)
|
||||
}
|
||||
case .ended, .cancelled, .failed:
|
||||
captured = false
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateViews(point: CGPoint) {
|
||||
pointsView.alpha = 1
|
||||
pointsView.center = CGPoint(x: point.x, y: bounds.midY)
|
||||
|
||||
let s = pointInfoView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||
pointInfoView.frame.size = s
|
||||
let y = max(pointInfoView.frame.height / 2 + 5,
|
||||
min(bounds.height - pointInfoView.frame.height / 2 - 5, bounds.height - lineInfo!.point.y));
|
||||
let orientationChangeX = pointInfoView.alignment == .left ? s.width + 40 : bounds.width - s.width - 40
|
||||
if point.x > orientationChangeX {
|
||||
pointInfoView.alignment = .left
|
||||
pointInfoView.center = CGPoint(x: point.x - s.width / 2 - 20, y: y)
|
||||
} else {
|
||||
pointInfoView.alignment = .right
|
||||
pointInfoView.center = CGPoint(x: point.x + s.width / 2 + 20, y: y)
|
||||
}
|
||||
var f = pointInfoView.frame
|
||||
if f.minX < 0 {
|
||||
f.origin.x = 0
|
||||
pointInfoView.frame = f
|
||||
} else if f.minX + f.width > bounds.width {
|
||||
f.origin.x = bounds.width - f.width
|
||||
pointInfoView.frame = f
|
||||
}
|
||||
let arrowPoint = convert(CGPoint(x: 0, y: bounds.height - lineInfo!.point.y), to: pointInfoView)
|
||||
pointInfoView.arrowY = arrowPoint.y
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
var pf = pointsView.frame
|
||||
pf.origin.y = bounds.minY
|
||||
pf.size.height = bounds.height
|
||||
pointsView.frame = pf
|
||||
|
||||
var mf = myPositionView.frame
|
||||
mf.origin.y = bounds.minY
|
||||
mf.size.height = bounds.height
|
||||
myPositionView.frame = mf
|
||||
|
||||
update(bounds.width * infoX)
|
||||
updateMyPosition()
|
||||
}
|
||||
}
|
||||
|
||||
extension ChartInfoView: UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return !captured
|
||||
}
|
||||
}
|
||||
94
iphone/Chart/Chart/Views/ChartInfo/ChartMyPositionView.swift
Normal file
94
iphone/Chart/Chart/Views/ChartInfo/ChartMyPositionView.swift
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import UIKit
|
||||
|
||||
class ChartMyPositionView: UIView {
|
||||
override class var layerClass: AnyClass { CAShapeLayer.self }
|
||||
var shapeLayer: CAShapeLayer { layer as! CAShapeLayer }
|
||||
var pinY: CGFloat = 0 {
|
||||
didSet {
|
||||
updatePin()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let pinView = MyPositionPinView(frame: CGRect(x: 0, y: 0, width: 12, height: 16))
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
shapeLayer.lineDashPattern = [3, 2]
|
||||
shapeLayer.lineWidth = 2
|
||||
shapeLayer.strokeColor = UIColor(red: 0.142, green: 0.614, blue: 0.95, alpha: 0.3).cgColor
|
||||
addSubview(pinView)
|
||||
transform = CGAffineTransform.identity.scaledBy(x: 1, y: -1)
|
||||
pinView.transform = CGAffineTransform.identity.scaledBy(x: 1, y: -1)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: bounds.width / 2, y: 0))
|
||||
path.addLine(to: CGPoint(x: bounds.width / 2, y: bounds.height))
|
||||
shapeLayer.path = path.cgPath
|
||||
|
||||
updatePin()
|
||||
}
|
||||
|
||||
private func updatePin() {
|
||||
pinView.center = CGPoint(x: bounds.midX, y: pinY + pinView.bounds.height / 2)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class MyPositionPinView: UIView {
|
||||
override class var layerClass: AnyClass { CAShapeLayer.self }
|
||||
var shapeLayer: CAShapeLayer { layer as! CAShapeLayer }
|
||||
|
||||
var path: UIBezierPath = {
|
||||
let p = UIBezierPath()
|
||||
p.addArc(withCenter: CGPoint(x: 6, y: 6),
|
||||
radius: 6,
|
||||
startAngle: -CGFloat.pi / 2,
|
||||
endAngle: atan(3.5 / 4.8733971724),
|
||||
clockwise: true)
|
||||
p.addLine(to: CGPoint(x: 6 + 0.75, y: 15.6614378))
|
||||
p.addArc(withCenter: CGPoint(x: 6, y: 15),
|
||||
radius: 1,
|
||||
startAngle: atan(0.6614378 / 0.75),
|
||||
endAngle: CGFloat.pi - atan(0.6614378 / 0.75),
|
||||
clockwise: true)
|
||||
p.addLine(to: CGPoint(x: 6 - 4.8733971724, y: 9.5))
|
||||
p.addArc(withCenter: CGPoint(x: 6, y: 6),
|
||||
radius: 6,
|
||||
startAngle: CGFloat.pi - atan(3.5 / 4.8733971724),
|
||||
endAngle: -CGFloat.pi / 2,
|
||||
clockwise: true)
|
||||
p.close()
|
||||
|
||||
p.append(UIBezierPath(ovalIn: CGRect(x: 3, y: 3, width: 6, height: 6)))
|
||||
return p
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
shapeLayer.lineWidth = 0
|
||||
shapeLayer.fillColor = UIColor(red: 0.142, green: 0.614, blue: 0.95, alpha: 0.5).cgColor
|
||||
shapeLayer.fillRule = .evenOdd
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let sx = bounds.width / path.bounds.width
|
||||
let sy = bounds.height / path.bounds.height
|
||||
let p = path.copy() as! UIBezierPath
|
||||
p.apply(CGAffineTransform(scaleX: sx, y: sy))
|
||||
shapeLayer.path = p.cgPath
|
||||
}
|
||||
}
|
||||
127
iphone/Chart/Chart/Views/ChartInfo/ChartPointInfoView.swift
Normal file
127
iphone/Chart/Chart/Views/ChartInfo/ChartPointInfoView.swift
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import UIKit
|
||||
|
||||
final class ChartPointInfoView: UIView {
|
||||
enum Alignment {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
private let distanceLabel = UILabel()
|
||||
private let altitudeLabel = UILabel()
|
||||
private let stackView = UIStackView()
|
||||
|
||||
private let maskLayer = CAShapeLayer()
|
||||
private var maskPath: UIBezierPath?
|
||||
|
||||
private let isInterfaceRightToLeft = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft
|
||||
|
||||
var arrowY: CGFloat? {
|
||||
didSet {
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
var alignment = Alignment.left {
|
||||
didSet {
|
||||
updateMask()
|
||||
}
|
||||
}
|
||||
|
||||
var font: UIFont = UIFont.systemFont(ofSize: 12, weight: .regular) {
|
||||
didSet {
|
||||
distanceLabel.font = font
|
||||
altitudeLabel.font = font
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor = .lightGray {
|
||||
didSet {
|
||||
distanceLabel.textColor = textColor
|
||||
altitudeLabel.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
override var backgroundColor: UIColor? {
|
||||
didSet {
|
||||
maskLayer.fillColor = backgroundColor?.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
layer.cornerRadius = 5
|
||||
backgroundColor = .clear
|
||||
layer.shadowColor = UIColor(white: 0, alpha: 1).cgColor
|
||||
layer.shadowOpacity = 0.25
|
||||
layer.shadowRadius = 2
|
||||
layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
maskLayer.fillColor = backgroundColor?.cgColor
|
||||
layer.addSublayer(maskLayer)
|
||||
|
||||
stackView.alignment = .leading
|
||||
stackView.axis = .vertical
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(stackView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leftAnchor.constraint(equalTo: leftAnchor, constant: 6),
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor, constant: 6),
|
||||
stackView.rightAnchor.constraint(equalTo: rightAnchor, constant: -6),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6)
|
||||
])
|
||||
|
||||
stackView.addArrangedSubview(distanceLabel)
|
||||
stackView.addArrangedSubview(altitudeLabel)
|
||||
stackView.setCustomSpacing(6, after: distanceLabel)
|
||||
|
||||
distanceLabel.font = font
|
||||
altitudeLabel.font = font
|
||||
|
||||
distanceLabel.textColor = textColor
|
||||
altitudeLabel.textColor = textColor
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func set(x: CGFloat, label: String, points: [ChartLineInfo]) {
|
||||
distanceLabel.text = label
|
||||
altitudeLabel.text = altitudeText(points[0])
|
||||
}
|
||||
|
||||
func update(x: CGFloat, label: String, points: [ChartLineInfo]) {
|
||||
distanceLabel.text = label
|
||||
altitudeLabel.text = altitudeText(points[0])
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
private func altitudeText(_ point: ChartLineInfo) -> String {
|
||||
return String(isInterfaceRightToLeft ? "\(point.formattedValue) ▲" : "▲ \(point.formattedValue)")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let y = arrowY ?? bounds.midY
|
||||
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 3)
|
||||
let trianglePath = UIBezierPath()
|
||||
trianglePath.move(to: CGPoint(x: bounds.maxX, y: y - 3))
|
||||
trianglePath.addLine(to: CGPoint(x: bounds.maxX + 5, y: y))
|
||||
trianglePath.addLine(to: CGPoint(x: bounds.maxX, y: y + 3))
|
||||
trianglePath.close()
|
||||
path.append(trianglePath)
|
||||
maskPath = path
|
||||
updateMask()
|
||||
}
|
||||
|
||||
private func updateMask() {
|
||||
guard let path = maskPath?.copy() as? UIBezierPath else { return }
|
||||
if alignment == .right {
|
||||
path.apply(CGAffineTransform.identity.scaledBy(x: -1, y: 1).translatedBy(x: -bounds.width, y: 0))
|
||||
}
|
||||
maskLayer.path = path.cgPath
|
||||
layer.shadowPath = path.cgPath
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import UIKit
|
||||
|
||||
fileprivate class CircleView: UIView {
|
||||
override class var layerClass: AnyClass { return CAShapeLayer.self }
|
||||
|
||||
var color: UIColor? {
|
||||
didSet {
|
||||
shapeLayer.fillColor = color?.withAlphaComponent(0.5).cgColor
|
||||
ringLayer.fillColor = UIColor.white.cgColor
|
||||
centerLayer.fillColor = color?.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
var shapeLayer: CAShapeLayer {
|
||||
return layer as! CAShapeLayer
|
||||
}
|
||||
|
||||
let ringLayer = CAShapeLayer()
|
||||
let centerLayer = CAShapeLayer()
|
||||
|
||||
override var frame: CGRect {
|
||||
didSet {
|
||||
let p = UIBezierPath(ovalIn: bounds)
|
||||
shapeLayer.path = p.cgPath
|
||||
ringLayer.frame = shapeLayer.bounds.insetBy(dx: shapeLayer.bounds.width / 6, dy: shapeLayer.bounds.height / 6)
|
||||
ringLayer.path = UIBezierPath(ovalIn: ringLayer.bounds).cgPath
|
||||
centerLayer.frame = shapeLayer.bounds.insetBy(dx: shapeLayer.bounds.width / 3, dy: shapeLayer.bounds.height / 3)
|
||||
centerLayer.path = UIBezierPath(ovalIn: centerLayer.bounds).cgPath
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
shapeLayer.fillColor = color?.withAlphaComponent(0.5).cgColor
|
||||
shapeLayer.lineWidth = 4
|
||||
shapeLayer.fillRule = .evenOdd
|
||||
shapeLayer.addSublayer(ringLayer)
|
||||
shapeLayer.addSublayer(centerLayer)
|
||||
ringLayer.fillColor = UIColor.white.cgColor
|
||||
centerLayer.fillColor = color?.cgColor
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
class ChartPointIntersectionView: UIView {
|
||||
fileprivate var intersectionView = CircleView()
|
||||
|
||||
var color: UIColor = UIColor(red: 0.14, green: 0.61, blue: 0.95, alpha: 1) {
|
||||
didSet {
|
||||
intersectionView.color = color
|
||||
backgroundColor = color.withAlphaComponent(0.5)
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = color.withAlphaComponent(0.5)
|
||||
transform = CGAffineTransform.identity.scaledBy(x: 1, y: -1)
|
||||
|
||||
intersectionView.color = color
|
||||
intersectionView.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
|
||||
intersectionView.center = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
addSubview(intersectionView)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func updatePoint(_ point: ChartLineInfo) {
|
||||
intersectionView.center = CGPoint(x: bounds.midX, y: point.point.y)
|
||||
color = point.color
|
||||
}
|
||||
}
|
||||
112
iphone/Chart/Chart/Views/ChartLineView.swift
Normal file
112
iphone/Chart/Chart/Views/ChartLineView.swift
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import UIKit
|
||||
|
||||
class ChartLineView: UIView {
|
||||
override class var layerClass: AnyClass { return CAShapeLayer.self }
|
||||
|
||||
private var minX = 0
|
||||
private var maxX = 0
|
||||
private var minY: CGFloat = 0
|
||||
private var maxY: CGFloat = 0
|
||||
|
||||
var isPreview = false
|
||||
|
||||
var lineWidth: CGFloat = 1 {
|
||||
didSet {
|
||||
shapeLayer.lineWidth = lineWidth
|
||||
}
|
||||
}
|
||||
|
||||
var chartLine: ChartPresentationLine! {
|
||||
didSet {
|
||||
guard let chartLine = chartLine else { return }
|
||||
maxX = chartLine.values.count - 1
|
||||
minY = chartLine.minY
|
||||
maxY = chartLine.maxY
|
||||
switch chartLine.type {
|
||||
case .line:
|
||||
shapeLayer.strokeColor = chartLine.color.cgColor
|
||||
shapeLayer.fillColor = UIColor.clear.cgColor
|
||||
shapeLayer.lineJoin = .round
|
||||
case .lineArea:
|
||||
shapeLayer.strokeColor = UIColor.clear.cgColor
|
||||
shapeLayer.fillColor = chartLine.color.cgColor
|
||||
}
|
||||
shapeLayer.lineWidth = lineWidth
|
||||
updateGraph()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
transform = CGAffineTransform.identity.scaledBy(x: 1, y: -1)
|
||||
isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
var shapeLayer: CAShapeLayer {
|
||||
return layer as! CAShapeLayer
|
||||
}
|
||||
|
||||
func setViewport(minX: Int, maxX: Int, minY: CGFloat, maxY: CGFloat, animationStyle: ChartAnimation = .none) {
|
||||
guard minX < maxX && minY < maxY else { return }
|
||||
self.minX = minX
|
||||
self.maxX = maxX
|
||||
self.minY = minY
|
||||
self.maxY = maxY
|
||||
updateGraph(animationStyle: animationStyle)
|
||||
}
|
||||
|
||||
func setX(min: Int, max: Int) {
|
||||
guard min < max else { return }
|
||||
minX = min
|
||||
maxX = max
|
||||
updateGraph()
|
||||
}
|
||||
|
||||
func setY(min: CGFloat, max: CGFloat, animationStyle: ChartAnimation = .none) {
|
||||
guard min < max else { return }
|
||||
minY = min
|
||||
maxY = max
|
||||
updateGraph(animationStyle: animationStyle)
|
||||
}
|
||||
|
||||
private func updateGraph(animationStyle: ChartAnimation = .none) {
|
||||
let p = isPreview ? chartLine.previewPath : chartLine.path
|
||||
guard let realPath = p.copy() as? UIBezierPath else { return }
|
||||
|
||||
let xScale = bounds.width / CGFloat(maxX - minX)
|
||||
let xTranslate = -bounds.width * CGFloat(minX) / CGFloat(maxX - minX)
|
||||
let yScale = (bounds.height - 1) / CGFloat(maxY - minY)
|
||||
let yTranslate = (bounds.height - 1) * CGFloat(chartLine.minY - minY) / CGFloat(maxY - minY) + 0.5
|
||||
let scale = CGAffineTransform.identity.scaledBy(x: xScale, y: yScale)
|
||||
let translate = CGAffineTransform.identity.translatedBy(x: xTranslate, y: yTranslate)
|
||||
let transform = scale.concatenating(translate)
|
||||
realPath.apply(transform)
|
||||
|
||||
if animationStyle != .none {
|
||||
let timingFunction = CAMediaTimingFunction(name: animationStyle == .interactive ? .linear : .easeInEaseOut)
|
||||
if shapeLayer.animationKeys()?.contains("path") ?? false,
|
||||
let presentation = shapeLayer.presentation(),
|
||||
let path = presentation.path {
|
||||
shapeLayer.removeAnimation(forKey: "path")
|
||||
shapeLayer.path = path
|
||||
}
|
||||
|
||||
let animation = CABasicAnimation(keyPath: "path")
|
||||
animation.duration = animationStyle.rawValue
|
||||
animation.fromValue = shapeLayer.path
|
||||
animation.timingFunction = timingFunction
|
||||
layer.add(animation, forKey: "path")
|
||||
}
|
||||
|
||||
shapeLayer.path = realPath.cgPath
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateGraph()
|
||||
}
|
||||
}
|
||||
246
iphone/Chart/Chart/Views/ChartPreviewView.swift
Normal file
246
iphone/Chart/Chart/Views/ChartPreviewView.swift
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
import UIKit
|
||||
|
||||
protocol ChartPreviewViewDelegate: AnyObject {
|
||||
func chartPreviewView(_ view: ChartPreviewView, didChangeMinX minX: Int, maxX: Int)
|
||||
}
|
||||
|
||||
class TintView: UIView {
|
||||
let maskLayer = CAShapeLayer()
|
||||
|
||||
override init(frame: CGRect = .zero) {
|
||||
super.init(frame: frame)
|
||||
maskLayer.fillRule = .evenOdd
|
||||
layer.mask = maskLayer
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func updateViewport(_ viewport: CGRect) {
|
||||
let cornersMask = UIBezierPath(roundedRect: bounds, cornerRadius: 5)
|
||||
let rectMask = UIBezierPath(rect: viewport.insetBy(dx: 11, dy: 1))
|
||||
let result = UIBezierPath()
|
||||
result.append(cornersMask)
|
||||
result.append(rectMask)
|
||||
result.usesEvenOddFillRule = true
|
||||
maskLayer.path = result.cgPath
|
||||
}
|
||||
}
|
||||
|
||||
class ViewPortView: ExpandedTouchView {
|
||||
let maskLayer = CAShapeLayer()
|
||||
var tintView: TintView?
|
||||
|
||||
override init(frame: CGRect = .zero) {
|
||||
super.init(frame: frame)
|
||||
maskLayer.fillRule = .evenOdd
|
||||
layer.mask = maskLayer
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override var frame: CGRect {
|
||||
didSet {
|
||||
maskLayer.path = makeMaskPath().cgPath
|
||||
tintView?.updateViewport(convert(bounds, to: tintView))
|
||||
}
|
||||
}
|
||||
|
||||
func makeMaskPath() -> UIBezierPath {
|
||||
let cornersMask = UIBezierPath(roundedRect: bounds, cornerRadius: 5)
|
||||
let rectMask = UIBezierPath(rect: bounds.insetBy(dx: 11, dy: 1))
|
||||
let result = UIBezierPath()
|
||||
result.append(cornersMask)
|
||||
result.append(rectMask)
|
||||
result.usesEvenOddFillRule = true
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class ChartPreviewView: ExpandedTouchView {
|
||||
let previewContainerView = UIView()
|
||||
let viewPortView = ViewPortView()
|
||||
let leftBoundView = UIView()
|
||||
let rightBoundView = UIView()
|
||||
let tintView = TintView()
|
||||
var previewViews: [ChartLineView] = []
|
||||
|
||||
var selectorColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
viewPortView.backgroundColor = selectorColor
|
||||
}
|
||||
}
|
||||
|
||||
var selectorTintColor: UIColor = UIColor.clear {
|
||||
didSet {
|
||||
tintView.backgroundColor = selectorTintColor
|
||||
}
|
||||
}
|
||||
|
||||
var minX = 0
|
||||
var maxX = 0
|
||||
weak var delegate: ChartPreviewViewDelegate?
|
||||
|
||||
override var frame: CGRect {
|
||||
didSet {
|
||||
if chartData != nil {
|
||||
updateViewPort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var chartData: ChartPresentationData! {
|
||||
didSet {
|
||||
previewViews.forEach { $0.removeFromSuperview() }
|
||||
previewViews.removeAll()
|
||||
for i in (0..<chartData.linesCount).reversed() {
|
||||
let line = chartData.lineAt(i)
|
||||
let v = ChartLineView()
|
||||
v.isPreview = true
|
||||
v.chartLine = line
|
||||
v.frame = previewContainerView.bounds
|
||||
v.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
previewContainerView.addSubview(v)
|
||||
previewViews.insert(v, at: 0)
|
||||
}
|
||||
previewViews.forEach { $0.setY(min: chartData.lower, max: chartData.upper) }
|
||||
let count = chartData.pointsCount - 1
|
||||
minX = 0
|
||||
maxX = count
|
||||
updateViewPort()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
previewContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
previewContainerView.layer.cornerRadius = 5
|
||||
previewContainerView.clipsToBounds = true
|
||||
addSubview(previewContainerView)
|
||||
let t = previewContainerView.topAnchor.constraint(equalTo: topAnchor)
|
||||
let b = previewContainerView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
t.priority = .defaultHigh
|
||||
b.priority = .defaultHigh
|
||||
t.constant = 1
|
||||
b.constant = -1
|
||||
NSLayoutConstraint.activate([
|
||||
previewContainerView.leftAnchor.constraint(equalTo: leftAnchor),
|
||||
previewContainerView.rightAnchor.constraint(equalTo: rightAnchor),
|
||||
t,
|
||||
b])
|
||||
|
||||
tintView.frame = bounds
|
||||
tintView.backgroundColor = selectorTintColor
|
||||
tintView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
addSubview(tintView)
|
||||
|
||||
viewPortView.tintView = tintView
|
||||
viewPortView.backgroundColor = selectorColor
|
||||
viewPortView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(viewPortView)
|
||||
|
||||
viewPortView.addSubview(leftBoundView)
|
||||
viewPortView.addSubview(rightBoundView)
|
||||
|
||||
let pan = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:)))
|
||||
viewPortView.addGestureRecognizer(pan)
|
||||
|
||||
let leftPan = UIPanGestureRecognizer(target: self, action: #selector(onLeftPan(_:)))
|
||||
let rightPan = UIPanGestureRecognizer(target: self, action: #selector(onRightPan(_:)))
|
||||
leftBoundView.addGestureRecognizer(leftPan)
|
||||
rightBoundView.addGestureRecognizer(rightPan)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@objc func onPan(_ sender: UIPanGestureRecognizer) {
|
||||
if sender.state != .changed { return }
|
||||
|
||||
let p = sender.translation(in: viewPortView)
|
||||
let count = chartData.labels.count - 1
|
||||
let x = Int((viewPortView.frame.minX + p.x) / bounds.width * CGFloat(count))
|
||||
let dx = maxX - minX
|
||||
let mx = x + dx
|
||||
|
||||
if x > 0 && mx < count {
|
||||
viewPortView.frame = viewPortView.frame.offsetBy(dx: p.x, dy: 0)
|
||||
sender.setTranslation(CGPoint(x: 0, y: 0), in: viewPortView)
|
||||
if x != minX {
|
||||
minX = x
|
||||
maxX = mx
|
||||
delegate?.chartPreviewView(self, didChangeMinX: minX, maxX: maxX)
|
||||
}
|
||||
} else if minX > 0 && x <= 0 {
|
||||
setX(min: 0, max: dx)
|
||||
} else if maxX < count && mx >= count {
|
||||
setX(min: count - dx, max: count)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func onLeftPan(_ sender: UIPanGestureRecognizer) {
|
||||
if sender.state != .changed { return }
|
||||
|
||||
let p = sender.translation(in: leftBoundView)
|
||||
let count = chartData.labels.count - 1
|
||||
let x = Int((viewPortView.frame.minX + p.x) / bounds.width * CGFloat(count))
|
||||
|
||||
if x > 0 && x < maxX && maxX - x >= count / 10 {
|
||||
var f = viewPortView.frame
|
||||
f = CGRect(x: f.minX + p.x, y: f.minY, width: f.width - p.x, height: f.height)
|
||||
viewPortView.frame = f
|
||||
rightBoundView.frame = CGRect(x: viewPortView.bounds.width - 14, y: 0, width: 44, height: viewPortView.bounds.height)
|
||||
sender.setTranslation(CGPoint(x: 0, y: 0), in: leftBoundView)
|
||||
if x != minX {
|
||||
minX = x
|
||||
delegate?.chartPreviewView(self, didChangeMinX: minX, maxX: maxX)
|
||||
}
|
||||
} else if x <= 0 && minX > 0 {
|
||||
setX(min: 0, max: maxX)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func onRightPan(_ sender: UIPanGestureRecognizer) {
|
||||
if sender.state != .changed { return }
|
||||
|
||||
let p = sender.translation(in: viewPortView)
|
||||
let count = chartData.labels.count - 1
|
||||
let x = Int((viewPortView.frame.maxX + p.x) / bounds.width * CGFloat(count))
|
||||
|
||||
if x > minX && x < count && x - minX >= count / 10 {
|
||||
var f = viewPortView.frame
|
||||
f = CGRect(x: f.minX, y: f.minY, width: f.width + p.x, height: f.height)
|
||||
viewPortView.frame = f
|
||||
rightBoundView.frame = CGRect(x: viewPortView.bounds.width - 14, y: 0, width: 44, height: viewPortView.bounds.height)
|
||||
sender.setTranslation(CGPoint(x: 0, y: 0), in: rightBoundView)
|
||||
if x != maxX {
|
||||
maxX = x
|
||||
delegate?.chartPreviewView(self, didChangeMinX: minX, maxX: maxX)
|
||||
}
|
||||
} else if x >= count && maxX < count {
|
||||
setX(min: minX, max: count)
|
||||
}
|
||||
}
|
||||
|
||||
func setX(min: Int, max: Int) {
|
||||
guard min < max else { return }
|
||||
minX = min
|
||||
maxX = max
|
||||
updateViewPort()
|
||||
delegate?.chartPreviewView(self, didChangeMinX: minX, maxX: maxX)
|
||||
}
|
||||
|
||||
func updateViewPort() {
|
||||
let count = CGFloat(chartData.labels.count - 1)
|
||||
viewPortView.frame = CGRect(x: CGFloat(minX) / count * bounds.width,
|
||||
y: bounds.minY,
|
||||
width: CGFloat(maxX - minX) / count * bounds.width,
|
||||
height: bounds.height)
|
||||
leftBoundView.frame = CGRect(x: -30, y: 0, width: 44, height: viewPortView.bounds.height)
|
||||
rightBoundView.frame = CGRect(x: viewPortView.bounds.width - 14, y: 0, width: 44, height: viewPortView.bounds.height)
|
||||
}
|
||||
}
|
||||
374
iphone/Chart/Chart/Views/ChartView.swift
Normal file
374
iphone/Chart/Chart/Views/ChartView.swift
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
import UIKit
|
||||
|
||||
enum ChartAnimation: TimeInterval {
|
||||
case none = 0.0
|
||||
case animated = 0.3
|
||||
case interactive = 0.1
|
||||
}
|
||||
|
||||
public class ChartView: UIView {
|
||||
let chartsContainerView = ExpandedTouchView()
|
||||
let chartPreviewView = ChartPreviewView()
|
||||
let yAxisView = ChartYAxisView()
|
||||
let xAxisView = ChartXAxisView()
|
||||
let chartInfoView = ChartInfoView()
|
||||
var lineViews: [ChartLineView] = []
|
||||
var showPreview: Bool = false // Set true to show the preview
|
||||
|
||||
private var tapGR: UITapGestureRecognizer!
|
||||
private var selectedPointDistance: Double = 0
|
||||
private var panStartPoint = 0
|
||||
private var panGR: UIPanGestureRecognizer!
|
||||
private var pinchStartLower = 0
|
||||
private var pinchStartUpper = 0
|
||||
private var pinchGR: UIPinchGestureRecognizer!
|
||||
|
||||
public var myPosition: Double = -1 {
|
||||
didSet {
|
||||
setMyPosition(myPosition)
|
||||
}
|
||||
}
|
||||
|
||||
public var previewSelectorColor: UIColor = UIColor.lightGray.withAlphaComponent(0.9) {
|
||||
didSet {
|
||||
chartPreviewView.selectorColor = previewSelectorColor
|
||||
}
|
||||
}
|
||||
|
||||
public var previewTintColor: UIColor = UIColor.lightGray.withAlphaComponent(0.5) {
|
||||
didSet {
|
||||
chartPreviewView.selectorTintColor = previewTintColor
|
||||
}
|
||||
}
|
||||
|
||||
public var infoBackgroundColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
chartInfoView.infoBackgroundColor = infoBackgroundColor
|
||||
yAxisView.textBackgroundColor = infoBackgroundColor.withAlphaComponent(0.7)
|
||||
}
|
||||
}
|
||||
|
||||
public var infoShadowColor: UIColor = UIColor.black {
|
||||
didSet {
|
||||
chartInfoView.infoShadowColor = infoShadowColor
|
||||
}
|
||||
}
|
||||
|
||||
public var infoShadowOpacity: Float = 0.25 {
|
||||
didSet {
|
||||
chartInfoView.infoShadowOpacity = infoShadowOpacity
|
||||
}
|
||||
}
|
||||
|
||||
public var font: UIFont = UIFont.systemFont(ofSize: 12, weight: .regular) {
|
||||
didSet {
|
||||
xAxisView.font = font
|
||||
yAxisView.font = font
|
||||
chartInfoView.font = font
|
||||
}
|
||||
}
|
||||
|
||||
public var textColor: UIColor = UIColor(white: 0, alpha: 0.2) {
|
||||
didSet {
|
||||
xAxisView.textColor = textColor
|
||||
yAxisView.textColor = textColor
|
||||
chartInfoView.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
public var gridColor: UIColor = UIColor(white: 0, alpha: 0.2) {
|
||||
didSet {
|
||||
yAxisView.gridColor = gridColor
|
||||
}
|
||||
}
|
||||
|
||||
public override var backgroundColor: UIColor? {
|
||||
didSet {
|
||||
chartInfoView.tooltipBackgroundColor = backgroundColor ?? .white
|
||||
}
|
||||
}
|
||||
|
||||
public var chartData: ChartPresentationData! {
|
||||
didSet {
|
||||
lineViews.forEach { $0.removeFromSuperview() }
|
||||
lineViews.removeAll()
|
||||
for i in (0..<chartData.linesCount).reversed() {
|
||||
let line = chartData.lineAt(i)
|
||||
let v = ChartLineView()
|
||||
v.clipsToBounds = true
|
||||
v.chartLine = line
|
||||
v.lineWidth = 3
|
||||
v.frame = chartsContainerView.bounds
|
||||
v.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
chartsContainerView.addSubview(v)
|
||||
lineViews.insert(v, at: 0)
|
||||
}
|
||||
|
||||
yAxisView.frame = chartsContainerView.bounds
|
||||
yAxisView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
yAxisView.transform = CGAffineTransform.identity.scaledBy(x: 1, y: -1)
|
||||
chartsContainerView.addSubview(yAxisView)
|
||||
|
||||
chartInfoView.frame = chartsContainerView.bounds
|
||||
chartInfoView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
chartInfoView.delegate = self
|
||||
chartInfoView.textColor = textColor
|
||||
chartsContainerView.addSubview(chartInfoView)
|
||||
|
||||
xAxisView.values = chartData.xAxisValues.enumerated().map { ChartXAxisView.Value(index: $0.offset, value: $0.element, text: chartData.labels[$0.offset]) }
|
||||
chartPreviewView.chartData = chartData
|
||||
xAxisView.setBounds(lower: chartPreviewView.minX, upper: chartPreviewView.maxX)
|
||||
updateCharts()
|
||||
}
|
||||
}
|
||||
|
||||
public var isChartViewInfoHidden: Bool = false {
|
||||
didSet {
|
||||
chartInfoView.isHidden = isChartViewInfoHidden
|
||||
chartInfoView.isUserInteractionEnabled = !isChartViewInfoHidden
|
||||
}
|
||||
}
|
||||
|
||||
public typealias OnSelectedPointChangedClosure = (_ px: CGFloat) -> Void
|
||||
public var onSelectedPointChanged: OnSelectedPointChangedClosure?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setup()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
setup()
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
isUserInteractionEnabled = false
|
||||
|
||||
xAxisView.font = font
|
||||
xAxisView.textColor = textColor
|
||||
yAxisView.font = font
|
||||
yAxisView.textColor = textColor
|
||||
yAxisView.gridColor = textColor
|
||||
chartInfoView.font = font
|
||||
chartPreviewView.selectorTintColor = previewTintColor
|
||||
chartPreviewView.selectorColor = previewSelectorColor
|
||||
chartInfoView.tooltipBackgroundColor = backgroundColor ?? .white
|
||||
yAxisView.textBackgroundColor = infoBackgroundColor.withAlphaComponent(0.7)
|
||||
|
||||
tapGR = UITapGestureRecognizer(target: self, action: #selector(onTap(_:)))
|
||||
chartsContainerView.addGestureRecognizer(tapGR)
|
||||
panGR = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:)))
|
||||
chartsContainerView.addGestureRecognizer(panGR)
|
||||
pinchGR = UIPinchGestureRecognizer(target: self, action: #selector(onPinch(_:)))
|
||||
chartsContainerView.addGestureRecognizer(pinchGR)
|
||||
addSubview(chartsContainerView)
|
||||
if showPreview {
|
||||
addSubview(chartPreviewView)
|
||||
}
|
||||
chartPreviewView.delegate = self
|
||||
addSubview(xAxisView)
|
||||
}
|
||||
|
||||
public func setSelectedPoint(_ x: Double) {
|
||||
guard selectedPointDistance != x else { return }
|
||||
selectedPointDistance = x
|
||||
let routeLength = chartData.xAxisValueAt(CGFloat(chartData.pointsCount - 1))
|
||||
let upper = chartData.xAxisValueAt(CGFloat(chartPreviewView.maxX))
|
||||
var lower = chartData.xAxisValueAt(CGFloat(chartPreviewView.minX))
|
||||
let rangeLength = upper - lower
|
||||
if x < lower || x > upper {
|
||||
let current = Double(chartInfoView.infoX) * rangeLength + lower
|
||||
let dx = x - current
|
||||
let dIdx = Int(dx / routeLength * Double(chartData.pointsCount))
|
||||
var lowerIdx = chartPreviewView.minX + dIdx
|
||||
var upperIdx = chartPreviewView.maxX + dIdx
|
||||
if lowerIdx < 0 {
|
||||
upperIdx -= lowerIdx
|
||||
lowerIdx = 0
|
||||
} else if upperIdx >= chartData.pointsCount {
|
||||
lowerIdx -= upperIdx - chartData.pointsCount - 1
|
||||
upperIdx = chartData.pointsCount - 1
|
||||
}
|
||||
chartPreviewView.setX(min: lowerIdx, max: upperIdx)
|
||||
lower = chartData.xAxisValueAt(CGFloat(chartPreviewView.minX))
|
||||
}
|
||||
chartInfoView.infoX = CGFloat((x - lower) / rangeLength)
|
||||
}
|
||||
|
||||
fileprivate func setMyPosition(_ x: Double) {
|
||||
let upper = chartData.xAxisValueAt(CGFloat(chartPreviewView.maxX))
|
||||
let lower = chartData.xAxisValueAt(CGFloat(chartPreviewView.minX))
|
||||
let rangeLength = upper - lower
|
||||
chartInfoView.myPositionX = CGFloat((x - lower) / rangeLength)
|
||||
}
|
||||
|
||||
override public func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let previewFrame = showPreview ? CGRect(x: bounds.minX, y: bounds.maxY - 30, width: bounds.width, height: 30) : .zero
|
||||
chartPreviewView.frame = previewFrame
|
||||
|
||||
let xAxisFrame = CGRect(x: bounds.minX, y: bounds.maxY - previewFrame.height - 26, width: bounds.width, height: 26)
|
||||
xAxisView.frame = xAxisFrame
|
||||
|
||||
let chartsFrame = CGRect(x: bounds.minX,
|
||||
y: bounds.minY,
|
||||
width: bounds.width,
|
||||
height: bounds.maxY - previewFrame.height - xAxisFrame.height)
|
||||
chartsContainerView.frame = chartsFrame
|
||||
}
|
||||
|
||||
override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
let rect = bounds.insetBy(dx: -30, dy: 0)
|
||||
return rect.contains(point)
|
||||
}
|
||||
|
||||
@objc func onTap(_ sender: UITapGestureRecognizer) {
|
||||
guard sender.state == .ended else {
|
||||
return
|
||||
}
|
||||
let point = sender.location(in: chartInfoView)
|
||||
updateCharts(animationStyle: .none)
|
||||
chartInfoView.update(point.x)
|
||||
chartInfoView(chartInfoView, didMoveToPoint: point.x)
|
||||
}
|
||||
|
||||
@objc func onPinch(_ sender: UIPinchGestureRecognizer) {
|
||||
if sender.state == .began {
|
||||
pinchStartLower = xAxisView.lowerBound
|
||||
pinchStartUpper = xAxisView.upperBound
|
||||
}
|
||||
|
||||
if sender.state != .changed {
|
||||
return
|
||||
}
|
||||
|
||||
let rangeLength = CGFloat(pinchStartUpper - pinchStartLower)
|
||||
let dx = Int(round((rangeLength * sender.scale - rangeLength) / 2))
|
||||
let lower = max(pinchStartLower + dx, 0)
|
||||
let upper = min(pinchStartUpper - dx, chartData.labels.count - 1)
|
||||
|
||||
guard upper - lower > max(1, chartData.labels.count / 10) else {
|
||||
return
|
||||
}
|
||||
|
||||
chartPreviewView.setX(min: lower, max: upper)
|
||||
xAxisView.setBounds(lower: lower, upper: upper)
|
||||
updateCharts(animationStyle: .none)
|
||||
chartInfoView.update()
|
||||
}
|
||||
|
||||
@objc func onPan(_ sender: UIPanGestureRecognizer) {
|
||||
let t = sender.translation(in: chartsContainerView)
|
||||
if sender.state == .began {
|
||||
panStartPoint = xAxisView.lowerBound
|
||||
}
|
||||
|
||||
if sender.state != .changed {
|
||||
return
|
||||
}
|
||||
|
||||
let dx = Int(round(t.x / chartsContainerView.bounds.width * CGFloat(xAxisView.upperBound - xAxisView.lowerBound)))
|
||||
let lower = panStartPoint - dx
|
||||
let upper = lower + xAxisView.upperBound - xAxisView.lowerBound
|
||||
if lower < 0 || upper > chartData.labels.count - 1 {
|
||||
return
|
||||
}
|
||||
|
||||
chartPreviewView.setX(min: lower, max: upper)
|
||||
xAxisView.setBounds(lower: lower, upper: upper)
|
||||
updateCharts(animationStyle: .none)
|
||||
chartInfoView.update()
|
||||
}
|
||||
|
||||
func updateCharts(animationStyle: ChartAnimation = .none) {
|
||||
var lower = CGFloat(Int.max)
|
||||
var upper = CGFloat(Int.min)
|
||||
|
||||
for i in 0..<chartData.linesCount {
|
||||
let line = chartData.lineAt(i)
|
||||
let subrange = line.aggregatedValues[xAxisView.lowerBound...xAxisView.upperBound]
|
||||
subrange.forEach {
|
||||
upper = max($0.y, upper)
|
||||
if line.type == .line || line.type == .lineArea {
|
||||
lower = min($0.y, lower)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let padding = round((upper - lower) / 10)
|
||||
lower = chartData.formatter.yAxisLowerBound(from: max(0, lower - padding))
|
||||
upper = chartData.formatter.yAxisUpperBound(from: upper + padding)
|
||||
let steps = chartData.formatter.yAxisSteps(lowerBound: lower, upperBound: upper)
|
||||
|
||||
if yAxisView.upperBound != upper || yAxisView.lowerBound != lower {
|
||||
yAxisView.setBounds(lower: lower,
|
||||
upper: upper,
|
||||
lowerLabel: chartData.formatter.yAxisString(from: Double(lower)),
|
||||
upperLabel: chartData.formatter.yAxisString(from: Double(upper)),
|
||||
steps: steps,
|
||||
animationStyle: animationStyle)
|
||||
}
|
||||
|
||||
lineViews.forEach {
|
||||
$0.setViewport(minX: xAxisView.lowerBound,
|
||||
maxX: xAxisView.upperBound,
|
||||
minY: lower,
|
||||
maxY: upper,
|
||||
animationStyle: animationStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChartView: ChartPreviewViewDelegate {
|
||||
func chartPreviewView(_ view: ChartPreviewView, didChangeMinX minX: Int, maxX: Int) {
|
||||
xAxisView.setBounds(lower: minX, upper: maxX)
|
||||
updateCharts(animationStyle: .none)
|
||||
chartInfoView.update()
|
||||
setMyPosition(myPosition)
|
||||
let x = chartInfoView.infoX * CGFloat(xAxisView.upperBound - xAxisView.lowerBound) + CGFloat(xAxisView.lowerBound)
|
||||
onSelectedPointChanged?(x)
|
||||
}
|
||||
}
|
||||
|
||||
extension ChartView: ChartInfoViewDelegate {
|
||||
func chartInfoView(_ view: ChartInfoView, didMoveToPoint pointX: CGFloat) {
|
||||
let p = convert(CGPoint(x: pointX, y: 0), from: view)
|
||||
let x = (p.x / bounds.width) * CGFloat(xAxisView.upperBound - xAxisView.lowerBound) + CGFloat(xAxisView.lowerBound)
|
||||
onSelectedPointChanged?(x)
|
||||
}
|
||||
|
||||
func chartInfoView(_ view: ChartInfoView, didCaptureInfoView captured: Bool) {
|
||||
panGR.isEnabled = !captured
|
||||
}
|
||||
|
||||
func chartInfoView(_ view: ChartInfoView, infoAtPointX pointX: CGFloat) -> (String, [ChartLineInfo])? {
|
||||
let p = convert(CGPoint(x: pointX, y: .zero), from: view)
|
||||
let x = (p.x / bounds.width) * CGFloat(xAxisView.upperBound - xAxisView.lowerBound) + CGFloat(xAxisView.lowerBound)
|
||||
let x1 = floor(x)
|
||||
let x2 = ceil(x)
|
||||
guard !pointX.isZero, Int(x1) < chartData.labels.count && x >= 0 else { return nil }
|
||||
let label = chartData.labelAt(x)
|
||||
|
||||
var result: [ChartLineInfo] = []
|
||||
for i in 0..<chartData.linesCount {
|
||||
let line = chartData.lineAt(i)
|
||||
guard line.type != .lineArea else { continue }
|
||||
let y1 = line.values.altitude(at: x1 / CGFloat(chartData.pointsCount))
|
||||
let y2 = line.values.altitude(at: x2 / CGFloat(chartData.pointsCount))
|
||||
|
||||
let dx = x - x1
|
||||
let y = dx * (y2 - y1) + y1
|
||||
let py = round(chartsContainerView.bounds.height * CGFloat(y - yAxisView.lowerBound) /
|
||||
CGFloat(yAxisView.upperBound - yAxisView.lowerBound))
|
||||
|
||||
let v = round(dx * CGFloat(y2 - y1)) + CGFloat(y1)
|
||||
result.append(ChartLineInfo(color: line.color,
|
||||
point: chartsContainerView.convert(CGPoint(x: p.x, y: py), to: view),
|
||||
formattedValue: chartData.formatter.yAxisString(from: Double(v))))
|
||||
}
|
||||
|
||||
return (label, result)
|
||||
}
|
||||
}
|
||||
130
iphone/Chart/Chart/Views/ChartXAxisView.swift
Normal file
130
iphone/Chart/Chart/Views/ChartXAxisView.swift
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import UIKit
|
||||
|
||||
fileprivate class ChartXAxisInnerView: UIView {
|
||||
var lowerBound = 0
|
||||
var upperBound = 0
|
||||
var steps: [String] = []
|
||||
var labels: [UILabel] = []
|
||||
|
||||
var font: UIFont = UIFont.systemFont(ofSize: 12, weight: .regular) {
|
||||
didSet {
|
||||
labels.forEach { $0.font = font }
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor = UIColor(white: 0, alpha: 0.3) {
|
||||
didSet {
|
||||
labels.forEach { $0.textColor = textColor }
|
||||
}
|
||||
}
|
||||
|
||||
override var frame: CGRect {
|
||||
didSet {
|
||||
if upperBound > 0 {
|
||||
updateLabels()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func makeLabel(text: String) -> UILabel {
|
||||
let label = UILabel()
|
||||
label.font = font
|
||||
label.textColor = textColor
|
||||
label.text = text
|
||||
label.frame = CGRect(x: 0, y: 0, width: 60, height: 15)
|
||||
return label
|
||||
}
|
||||
|
||||
func setBounds(lower: Int, upper: Int, steps: [String]) {
|
||||
lowerBound = lower
|
||||
upperBound = upper
|
||||
self.steps = steps
|
||||
|
||||
labels.forEach { $0.removeFromSuperview() }
|
||||
labels.removeAll()
|
||||
|
||||
for i in 0..<steps.count {
|
||||
let step = steps[i]
|
||||
let label = makeLabel(text: step)
|
||||
if i == 0 {
|
||||
label.textAlignment = .left
|
||||
} else if i == steps.count - 1 {
|
||||
label.textAlignment = .right
|
||||
} else {
|
||||
label.textAlignment = .center
|
||||
}
|
||||
labels.append(label)
|
||||
addSubview(label)
|
||||
}
|
||||
|
||||
updateLabels()
|
||||
}
|
||||
|
||||
private func updateLabels() {
|
||||
let step = CGFloat(upperBound - lowerBound) / CGFloat(labels.count - 1)
|
||||
for i in 0..<labels.count {
|
||||
let x = bounds.width * step * CGFloat(i) / CGFloat(upperBound - lowerBound)
|
||||
let l = labels[i]
|
||||
var f = l.frame
|
||||
let adjust = bounds.width > 0 ? x / bounds.width : 0
|
||||
f.origin = CGPoint(x: x - f.width * adjust, y: 0)
|
||||
l.frame = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChartXAxisView: UIView {
|
||||
|
||||
struct Value {
|
||||
let index: Int
|
||||
let value: Double
|
||||
let text: String
|
||||
}
|
||||
|
||||
var lowerBound = 0
|
||||
var upperBound = 0
|
||||
var values: [Value] = []
|
||||
|
||||
var font: UIFont = UIFont.systemFont(ofSize: 12, weight: .regular) {
|
||||
didSet {
|
||||
labelsView?.font = font
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor = UIColor(white: 0, alpha: 0.3) {
|
||||
didSet {
|
||||
labelsView?.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
private var labelsView: ChartXAxisInnerView?
|
||||
|
||||
func setBounds(lower: Int, upper: Int) {
|
||||
lowerBound = lower
|
||||
upperBound = upper
|
||||
|
||||
let begin = values[lower].value
|
||||
let end = values[upper].value
|
||||
let step = CGFloat(end - begin) / 5
|
||||
var labels: [String] = []
|
||||
for i in 0..<5 {
|
||||
if let x = values.first(where: { $0.value >= (begin + step * CGFloat(i)) }) {
|
||||
labels.append(x.text)
|
||||
}
|
||||
}
|
||||
labels.append(values[upper].text)
|
||||
|
||||
let lv = ChartXAxisInnerView()
|
||||
lv.frame = bounds
|
||||
lv.textColor = textColor
|
||||
lv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
addSubview(lv)
|
||||
|
||||
if let labelsView = labelsView {
|
||||
labelsView.removeFromSuperview()
|
||||
}
|
||||
|
||||
lv.setBounds(lower: lower, upper: upper, steps: labels)
|
||||
labelsView = lv
|
||||
}
|
||||
}
|
||||
252
iphone/Chart/Chart/Views/ChartYAxisView.swift
Normal file
252
iphone/Chart/Chart/Views/ChartYAxisView.swift
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import UIKit
|
||||
|
||||
enum ChartYAxisViewAlignment {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
fileprivate class ChartYAxisInnerView: UIView {
|
||||
override class var layerClass: AnyClass { return CAShapeLayer.self }
|
||||
|
||||
private static let font = UIFont.systemFont(ofSize: 12, weight: .regular)
|
||||
var lowerBound: CGFloat = 0
|
||||
var upperBound: CGFloat = 0
|
||||
var steps: [CGFloat] = []
|
||||
let lowerLabel: UILabel
|
||||
let upperLabel: UILabel
|
||||
let lowerLabelBackground = UIView()
|
||||
let upperLabelBackground = UIView()
|
||||
var alignment: ChartYAxisViewAlignment = .left
|
||||
|
||||
var font: UIFont = UIFont.systemFont(ofSize: 12, weight: .regular) {
|
||||
didSet {
|
||||
lowerLabel.font = font
|
||||
upperLabel.font = font
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor = UIColor(white: 0, alpha: 0.3) {
|
||||
didSet {
|
||||
lowerLabel.textColor = textColor
|
||||
upperLabel.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
var textBackgroundColor: UIColor = UIColor(white: 1, alpha: 0.7) {
|
||||
didSet {
|
||||
lowerLabelBackground.backgroundColor = textBackgroundColor
|
||||
upperLabelBackground.backgroundColor = textBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
var gridColor: UIColor = UIColor.white {
|
||||
didSet {
|
||||
shapeLayer.strokeColor = gridColor.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
private var path: UIBezierPath?
|
||||
|
||||
var shapeLayer: CAShapeLayer {
|
||||
return layer as! CAShapeLayer
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
lowerLabel = ChartYAxisInnerView.makeLabel()
|
||||
upperLabel = ChartYAxisInnerView.makeLabel()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
lowerLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
upperLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
lowerLabelBackground.translatesAutoresizingMaskIntoConstraints = false
|
||||
upperLabelBackground.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
lowerLabelBackground.addSubview(lowerLabel)
|
||||
upperLabelBackground.addSubview(upperLabel)
|
||||
addSubview(lowerLabelBackground)
|
||||
addSubview(upperLabelBackground)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
lowerLabel.leftAnchor.constraint(equalTo: lowerLabelBackground.leftAnchor, constant: 5),
|
||||
lowerLabel.topAnchor.constraint(equalTo: lowerLabelBackground.topAnchor),
|
||||
lowerLabel.rightAnchor.constraint(equalTo: lowerLabelBackground.rightAnchor, constant: -5),
|
||||
lowerLabel.bottomAnchor.constraint(equalTo: lowerLabelBackground.bottomAnchor),
|
||||
|
||||
upperLabel.leftAnchor.constraint(equalTo: upperLabelBackground.leftAnchor, constant: 5),
|
||||
upperLabel.topAnchor.constraint(equalTo: upperLabelBackground.topAnchor),
|
||||
upperLabel.rightAnchor.constraint(equalTo: upperLabelBackground.rightAnchor, constant: -5),
|
||||
upperLabel.bottomAnchor.constraint(equalTo: upperLabelBackground.bottomAnchor),
|
||||
|
||||
lowerLabelBackground.topAnchor.constraint(equalTo: topAnchor, constant: 5),
|
||||
lowerLabelBackground.rightAnchor.constraint(equalTo: rightAnchor, constant: -5),
|
||||
upperLabelBackground.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5),
|
||||
upperLabelBackground.rightAnchor.constraint(equalTo: rightAnchor, constant: -5)
|
||||
])
|
||||
|
||||
lowerLabel.textColor = textColor
|
||||
upperLabel.textColor = textColor
|
||||
lowerLabelBackground.backgroundColor = textBackgroundColor
|
||||
upperLabelBackground.backgroundColor = textBackgroundColor
|
||||
shapeLayer.fillColor = UIColor.clear.cgColor
|
||||
shapeLayer.strokeColor = gridColor.cgColor
|
||||
shapeLayer.lineDashPattern = [2, 3]
|
||||
shapeLayer.lineWidth = 1
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
if upperBound > 0 && lowerBound > 0 {
|
||||
updateGrid()
|
||||
}
|
||||
|
||||
lowerLabelBackground.layer.cornerRadius = lowerLabelBackground.frame.height / 2
|
||||
upperLabelBackground.layer.cornerRadius = upperLabelBackground.frame.height / 2
|
||||
}
|
||||
|
||||
static func makeLabel() -> UILabel {
|
||||
let label = UILabel()
|
||||
label.font = ChartYAxisInnerView.font
|
||||
label.transform = CGAffineTransform.identity.scaledBy(x: 1, y: -1)
|
||||
return label
|
||||
}
|
||||
|
||||
func setBounds(lower: CGFloat, upper: CGFloat, lowerLabelText: String, upperLabelText: String, steps: [CGFloat]) {
|
||||
lowerBound = lower
|
||||
upperBound = upper
|
||||
lowerLabel.text = lowerLabelText
|
||||
upperLabel.text = upperLabelText
|
||||
self.steps = steps
|
||||
|
||||
updateGrid()
|
||||
}
|
||||
|
||||
func updateBounds(lower: CGFloat, upper: CGFloat, animationStyle: ChartAnimation = .none) {
|
||||
lowerBound = lower
|
||||
upperBound = upper
|
||||
updateGrid(animationStyle: animationStyle)
|
||||
}
|
||||
|
||||
func updateGrid(animationStyle: ChartAnimation = .none) {
|
||||
let p = UIBezierPath()
|
||||
for step in steps {
|
||||
p.move(to: CGPoint(x: 0, y: step))
|
||||
p.addLine(to: CGPoint(x: bounds.width, y: step))
|
||||
}
|
||||
|
||||
let realPath = p
|
||||
|
||||
let yScale = (bounds.height) / CGFloat(upperBound - lowerBound)
|
||||
let yTranslate = (bounds.height) * CGFloat(-lowerBound) / CGFloat(upperBound - lowerBound)
|
||||
let scale = CGAffineTransform.identity.scaledBy(x: 1, y: yScale)
|
||||
let translate = CGAffineTransform.identity.translatedBy(x: 0, y: yTranslate)
|
||||
let transform = scale.concatenating(translate)
|
||||
realPath.apply(transform)
|
||||
|
||||
if animationStyle != .none {
|
||||
let timingFunction = CAMediaTimingFunction(name: animationStyle == .interactive ? .linear : .easeInEaseOut)
|
||||
if shapeLayer.animationKeys()?.contains("path") ?? false,
|
||||
let presentation = shapeLayer.presentation(),
|
||||
let path = presentation.path {
|
||||
shapeLayer.removeAnimation(forKey: "path")
|
||||
shapeLayer.path = path
|
||||
}
|
||||
|
||||
let animation = CABasicAnimation(keyPath: "path")
|
||||
let duration = animationStyle.rawValue
|
||||
animation.duration = duration
|
||||
animation.fromValue = shapeLayer.path
|
||||
animation.timingFunction = timingFunction
|
||||
layer.add(animation, forKey: "path")
|
||||
}
|
||||
|
||||
shapeLayer.path = realPath.cgPath
|
||||
}
|
||||
}
|
||||
|
||||
class ChartYAxisView: UIView {
|
||||
var lowerBound: CGFloat = 0
|
||||
var upperBound: CGFloat = 0
|
||||
var alignment: ChartYAxisViewAlignment = .right
|
||||
|
||||
var font: UIFont = UIFont.systemFont(ofSize: 12, weight: .regular) {
|
||||
didSet {
|
||||
gridView?.font = font
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor = UIColor(white: 0, alpha: 0.3) {
|
||||
didSet {
|
||||
gridView?.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
var textBackgroundColor: UIColor = UIColor(white: 0, alpha: 0.3) {
|
||||
didSet {
|
||||
gridView?.textBackgroundColor = textBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
var gridColor: UIColor = UIColor(white: 0, alpha: 0.3) {
|
||||
didSet {
|
||||
gridView?.gridColor = gridColor
|
||||
}
|
||||
}
|
||||
|
||||
override var frame: CGRect {
|
||||
didSet {
|
||||
gridView?.updateGrid()
|
||||
}
|
||||
}
|
||||
|
||||
private var gridView: ChartYAxisInnerView?
|
||||
|
||||
func setBounds(lower: CGFloat,
|
||||
upper: CGFloat,
|
||||
lowerLabel: String,
|
||||
upperLabel: String,
|
||||
steps: [CGFloat],
|
||||
animationStyle: ChartAnimation = .none) {
|
||||
let gv = ChartYAxisInnerView()
|
||||
gv.alignment = alignment
|
||||
gv.textColor = textColor
|
||||
gv.gridColor = gridColor
|
||||
gv.textBackgroundColor = textBackgroundColor
|
||||
gv.frame = bounds
|
||||
gv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
addSubview(gv)
|
||||
|
||||
if let gridView = gridView {
|
||||
if animationStyle == .animated {
|
||||
gv.setBounds(lower: lowerBound,
|
||||
upper: upperBound,
|
||||
lowerLabelText: lowerLabel,
|
||||
upperLabelText: upperLabel,
|
||||
steps: steps)
|
||||
gv.alpha = 0
|
||||
gv.updateBounds(lower: lower, upper:upper, animationStyle: animationStyle)
|
||||
gridView.updateBounds(lower: lower, upper:upper, animationStyle: animationStyle)
|
||||
UIView.animate(withDuration: animationStyle.rawValue, animations: {
|
||||
gv.alpha = 1
|
||||
gridView.alpha = 0
|
||||
}) { _ in
|
||||
gridView.removeFromSuperview()
|
||||
}
|
||||
} else {
|
||||
gv.setBounds(lower: lower, upper: upper, lowerLabelText: lowerLabel, upperLabelText: upperLabel, steps: steps)
|
||||
gridView.removeFromSuperview()
|
||||
}
|
||||
} else {
|
||||
gv.setBounds(lower: lower, upper: upper, lowerLabelText: lowerLabel, upperLabelText: upperLabel, steps: steps)
|
||||
}
|
||||
|
||||
gridView = gv
|
||||
lowerBound = lower
|
||||
upperBound = upper
|
||||
}
|
||||
}
|
||||
8
iphone/Chart/Chart/Views/ExpandedTouchView.swift
Normal file
8
iphone/Chart/Chart/Views/ExpandedTouchView.swift
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import UIKit
|
||||
|
||||
class ExpandedTouchView: UIView {
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
let rect = bounds.insetBy(dx: -30, dy: 0)
|
||||
return rect.contains(point)
|
||||
}
|
||||
}
|
||||
2
iphone/Chart/Chart/ar.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/ar.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "المسافة";
|
||||
"you" = "أنت";
|
||||
2
iphone/Chart/Chart/cs.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/cs.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Vzdálenost";
|
||||
"you" = "Vy";
|
||||
2
iphone/Chart/Chart/da.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/da.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Afstand";
|
||||
"you" = "Du";
|
||||
2
iphone/Chart/Chart/de.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/de.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Entfernung";
|
||||
"you" = "Sie";
|
||||
2
iphone/Chart/Chart/el.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/el.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distance";
|
||||
"you" = "Εσείς";
|
||||
2
iphone/Chart/Chart/en-GB.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/en-GB.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distance";
|
||||
"you" = "You";
|
||||
2
iphone/Chart/Chart/en.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/en.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distance";
|
||||
"you" = "You";
|
||||
2
iphone/Chart/Chart/es.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/es.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distancia";
|
||||
"you" = "Usted";
|
||||
2
iphone/Chart/Chart/fi.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/fi.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Etäisyys";
|
||||
"you" = "Sinä";
|
||||
2
iphone/Chart/Chart/fr.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/fr.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distance";
|
||||
"you" = "Vous";
|
||||
2
iphone/Chart/Chart/hu.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/hu.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Távolság";
|
||||
"you" = "Ön";
|
||||
2
iphone/Chart/Chart/id.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/id.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Jarak";
|
||||
"you" = "Anda";
|
||||
2
iphone/Chart/Chart/it.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/it.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distanza";
|
||||
"you" = "Voi";
|
||||
2
iphone/Chart/Chart/ja.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/ja.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "距離";
|
||||
"you" = "君";
|
||||
2
iphone/Chart/Chart/ko.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/ko.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "거리";
|
||||
"you" = "당신";
|
||||
2
iphone/Chart/Chart/nb.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/nb.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Avstand";
|
||||
"you" = "Du";
|
||||
2
iphone/Chart/Chart/nl.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/nl.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Afstand";
|
||||
"you" = "Je";
|
||||
2
iphone/Chart/Chart/pl.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/pl.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Dystans";
|
||||
"you" = "Ty";
|
||||
2
iphone/Chart/Chart/pt.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/pt.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distância";
|
||||
"you" = "Você";
|
||||
2
iphone/Chart/Chart/ro.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/ro.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Distanță";
|
||||
"you" = "Tu";
|
||||
2
iphone/Chart/Chart/ru.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/ru.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Расстояние";
|
||||
"you" = "Вы";
|
||||
2
iphone/Chart/Chart/sk.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/sk.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Vzdialenosť";
|
||||
"you" = "Vy";
|
||||
2
iphone/Chart/Chart/sv.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/sv.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Avstånd";
|
||||
"you" = "Du";
|
||||
2
iphone/Chart/Chart/th.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/th.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "ระยะห่าง";
|
||||
"you" = "คุณ";
|
||||
2
iphone/Chart/Chart/tr.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/tr.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Mesafe";
|
||||
"you" = "Sen";
|
||||
2
iphone/Chart/Chart/uk.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/uk.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Відстань";
|
||||
"you" = "Ви";
|
||||
2
iphone/Chart/Chart/vi.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/vi.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "Khoảng cách";
|
||||
"you" = "Bạn";
|
||||
2
iphone/Chart/Chart/zh-Hans.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/zh-Hans.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "距离";
|
||||
"you" = "您";
|
||||
2
iphone/Chart/Chart/zh-Hant.lproj/Localizable.strings
Normal file
2
iphone/Chart/Chart/zh-Hant.lproj/Localizable.strings
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"placepage_distance" = "距离";
|
||||
"you" = "您";
|
||||
Loading…
Add table
Add a link
Reference in a new issue