Skip to content

Commit 6dd8846

Browse files
committed
Optimize BubbleChartOptionsComposer
1 parent 0cb940d commit 6dd8846

File tree

3 files changed

+282
-264
lines changed

3 files changed

+282
-264
lines changed

AAInfographicsDemo/Demo/AAChartModel/Composer/ChartSampleProvider.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ class ChartSampleProvider: NSObject {
186186
BubbleLegendChartOptionsComposer.packedbubbleSplitChart(),
187187
BubbleLegendChartOptionsComposer.packedbubbleSpiralChart(),
188188

189+
BubbleChartOptionsComposer.punchCardChart(),
190+
BubbleChartOptionsComposer.punchCardChart2(),
191+
BubbleChartOptionsComposer.punchCardChart3(),
192+
193+
194+
189195
DrawChartWithAAOptionsVC.configureLegendStyle(),
190196
DrawChartWithAAOptionsVC.simpleGaugeChart(),
191197
DrawChartWithAAOptionsVC.gaugeChartWithPlotBand(),

AAInfographicsDemo/Demo/AAOptions/BubbleChartOptionsComposer.swift

Lines changed: 275 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ class BubbleChartOptionsComposer {
178178

179179
var highchartsYAxes: [AAYAxis] = []
180180
var highchartsSeries: [AASeriesElement] = []
181-
let axisMarginLeft: Double = 150 // Space for day labels
181+
// let axisMarginLeft: Double = 150 // Space for day labels
182182

183183
// Calculate min/max commit count for size mapping
184184
var minCommit: Double = Double.infinity
@@ -271,7 +271,7 @@ class BubbleChartOptionsComposer {
271271
// .marginLeft(axisMarginLeft)
272272
)
273273
.title(AATitle()
274-
.text("Punch Card")) // No main title
274+
.text("Punch Card of Github"))
275275
.xAxis(AAXAxis() // X Axis config (same as scatter version)
276276
.categories(hours)
277277
.opposite(true)
@@ -311,4 +311,277 @@ class BubbleChartOptionsComposer {
311311
.series(highchartsSeries) // Use the array of Bubble series
312312
}
313313

314+
/*
315+
// prettier-ignore
316+
const hours = [
317+
'12a', '1a', '2a', '3a', '4a', '5a', '6a',
318+
'7a', '8a', '9a', '10a', '11a',
319+
'12p', '1p', '2p', '3p', '4p', '5p',
320+
'6p', '7p', '8p', '9p', '10p', '11p'
321+
];
322+
// prettier-ignore
323+
const days = [
324+
'Saturday', 'Friday', 'Thursday',
325+
'Wednesday', 'Tuesday', 'Monday', 'Sunday'
326+
];
327+
// prettier-ignore
328+
const data = [[0, 0, 5], [0, 1, 1], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 2], [0, 12, 4], [0, 13, 1], [0, 14, 1], [0, 15, 3], [0, 16, 4], [0, 17, 6], [0, 18, 4], [0, 19, 4], [0, 20, 3], [0, 21, 3], [0, 22, 2], [0, 23, 5], [1, 0, 7], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [1, 6, 0], [1, 7, 0], [1, 8, 0], [1, 9, 0], [1, 10, 5], [1, 11, 2], [1, 12, 2], [1, 13, 6], [1, 14, 9], [1, 15, 11], [1, 16, 6], [1, 17, 7], [1, 18, 8], [1, 19, 12], [1, 20, 5], [1, 21, 5], [1, 22, 7], [1, 23, 2], [2, 0, 1], [2, 1, 1], [2, 2, 0], [2, 3, 0], [2, 4, 0], [2, 5, 0], [2, 6, 0], [2, 7, 0], [2, 8, 0], [2, 9, 0], [2, 10, 3], [2, 11, 2], [2, 12, 1], [2, 13, 9], [2, 14, 8], [2, 15, 10], [2, 16, 6], [2, 17, 5], [2, 18, 5], [2, 19, 5], [2, 20, 7], [2, 21, 4], [2, 22, 2], [2, 23, 4], [3, 0, 7], [3, 1, 3], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [3, 6, 0], [3, 7, 0], [3, 8, 1], [3, 9, 0], [3, 10, 5], [3, 11, 4], [3, 12, 7], [3, 13, 14], [3, 14, 13], [3, 15, 12], [3, 16, 9], [3, 17, 5], [3, 18, 5], [3, 19, 10], [3, 20, 6], [3, 21, 4], [3, 22, 4], [3, 23, 1], [4, 0, 1], [4, 1, 3], [4, 2, 0], [4, 3, 0], [4, 4, 0], [4, 5, 1], [4, 6, 0], [4, 7, 0], [4, 8, 0], [4, 9, 2], [4, 10, 4], [4, 11, 4], [4, 12, 2], [4, 13, 4], [4, 14, 4], [4, 15, 14], [4, 16, 12], [4, 17, 1], [4, 18, 8], [4, 19, 5], [4, 20, 3], [4, 21, 7], [4, 22, 3], [4, 23, 0], [5, 0, 2], [5, 1, 1], [5, 2, 0], [5, 3, 3], [5, 4, 0], [5, 5, 0], [5, 6, 0], [5, 7, 0], [5, 8, 2], [5, 9, 0], [5, 10, 4], [5, 11, 1], [5, 12, 5], [5, 13, 10], [5, 14, 5], [5, 15, 7], [5, 16, 11], [5, 17, 6], [5, 18, 0], [5, 19, 5], [5, 20, 3], [5, 21, 4], [5, 22, 2], [5, 23, 0], [6, 0, 1], [6, 1, 0], [6, 2, 0], [6, 3, 0], [6, 4, 0], [6, 5, 0], [6, 6, 0], [6, 7, 0], [6, 8, 0], [6, 9, 0], [6, 10, 1], [6, 11, 0], [6, 12, 2], [6, 13, 1], [6, 14, 3], [6, 15, 4], [6, 16, 0], [6, 17, 0], [6, 18, 0], [6, 19, 0], [6, 20, 1], [6, 21, 2], [6, 22, 2], [6, 23, 6]];
329+
330+
// Process data for Highcharts bubble series
331+
const highchartsData = data
332+
.filter(item => item[2] > 0) // Filter out items with 0 commits
333+
.map(item => {
334+
const dayIndex = item[0];
335+
const hourIndex = item[1];
336+
const commitCount = item[2];
337+
return {
338+
x: hourIndex, // Angle axis category index (hour)
339+
y: dayIndex, // Radius axis category index (day)
340+
z: commitCount * 2, // Bubble size value (scaled commit count)
341+
// Store original data for easy access in tooltip
342+
commitCount: commitCount,
343+
dayName: days[dayIndex],
344+
hourName: hours[hourIndex]
345+
};
346+
});
347+
348+
// Find max Z value to set maxSize appropriately (optional but good for scaling)
349+
const maxZ = highchartsData.reduce((max, p) => Math.max(max, p.z), 0);
350+
351+
// Create the Highcharts chart
352+
Highcharts.chart('container', { // Assumes you have a <div id="container"></div> in your HTML
353+
chart: {
354+
polar: true,
355+
type: 'bubble',
356+
// Optional: Adjust plot area size if needed
357+
// marginBottom: 100 // Add space if labels overlap
358+
},
359+
360+
title: {
361+
text: 'Punch Card of Github'
362+
},
363+
364+
// Optional Pane configuration (circular plot area)
365+
pane: {
366+
startAngle: 0,
367+
endAngle: 360,
368+
// size: '85%' // Adjust size if needed
369+
},
370+
371+
legend: {
372+
// Echarts example had legend on right, but it's less useful for single series
373+
// align: 'right',
374+
// verticalAlign: 'middle',
375+
// layout: 'vertical'
376+
enabled: false // Disable legend for cleaner look
377+
},
378+
379+
xAxis: { // Angle Axis (Hours)
380+
categories: hours,
381+
tickmarkPlacement: 'on', // Place ticks on the category mark
382+
lineWidth: 0, // Hide axis line (like ECharts)
383+
gridLineWidth: 1, // Show radial grid lines (splitLine in ECharts)
384+
labels: {
385+
// No rotation needed usually for angle axis
386+
// rotation: 0 // Default
387+
},
388+
min: 0, // Ensure axis starts at the first category
389+
max: hours.length - 1 // Ensure axis ends at the last category
390+
},
391+
392+
yAxis: { // Radius Axis (Days)
393+
categories: days,
394+
// Important: Highcharts category axis plots 0 index at the *center* in polar.
395+
// Since ECharts `days` array starts with Saturday (index 0) and plots it innermost,
396+
// `reversed: false` is correct here.
397+
reversed: false,
398+
min: 0, // Ensure axis starts at the first category (center)
399+
max: days.length - 1, // Ensure axis ends at the last category (outermost)
400+
lineWidth: 0, // Hide axis line
401+
gridLineInterpolation: 'polygon', // Makes circular grid lines look better
402+
gridLineWidth: 1, // Show circular grid lines
403+
labels: {
404+
// Rotate labels like ECharts, adjust angle for best fit
405+
rotation: -45, // Negative angle often looks better on radius axis
406+
align: 'right',
407+
reserveSpace: true, // Important for rotated labels
408+
style: {
409+
// textOverflow: 'none' // Prevent ellipsis if labels are long
410+
},
411+
distance: 5 // Adjust distance from axis if needed
412+
},
413+
// Optional: Define tick positions explicitly if needed, but categories usually handle this
414+
// tickPositions: days.map((_, i) => i)
415+
},
416+
417+
tooltip: {
418+
useHTML: true, // Allows richer HTML formatting if needed
419+
// No header needed as we format the whole thing
420+
headerFormat: '',
421+
// Point formatter provides access to point data easily
422+
pointFormatter: function () {
423+
// Access the custom properties stored on the point
424+
return this.commitCount + ' commits in ' + this.hourName + ' of ' + this.dayName;
425+
}
426+
// Alternatively using the main formatter:
427+
// formatter: function() {
428+
// return this.point.commitCount + ' commits in ' + this.point.hourName + ' of ' + this.point.dayName;
429+
// }
430+
},
431+
432+
plotOptions: {
433+
bubble: {
434+
minSize: 0, // Bubbles with z=0 will have 0 size
435+
maxSize: Math.max(maxZ, 20), // Scale max size based on data, ensure minimum reasonable size
436+
// Using zThreshold: 0 might also help ensure 0 value isn't shown
437+
zThreshold: 0.1, // Values below this are treated as minSize
438+
dataLabels: { // Hide data labels on bubbles
439+
enabled: false
440+
},
441+
// Optional: Animation like Echarts (Highcharts animates by default)
442+
// animation: {
443+
// duration: 1000 // Adjust duration
444+
// }
445+
// Highcharts doesn't have a direct equivalent to ECharts' staggered animationDelay function
446+
// without custom logic or plugins. Default animation applies to all points.
447+
},
448+
series: {
449+
clip: false // Prevent bubbles near the edge from being clipped
450+
}
451+
},
452+
453+
series: [{
454+
name: 'Punch Card', // Name for the series (used in tooltip header if not formatted out)
455+
data: highchartsData
456+
}]
457+
});
458+
*/
459+
// MARK: - Polar Bubble Chart (Punch Card)
460+
static func punchCardChart2() -> AAOptions {
461+
// Data from the JavaScript example
462+
let hours = [
463+
"12a", "1a", "2a", "3a", "4a", "5a", "6a",
464+
"7a", "8a", "9a", "10a", "11a",
465+
"12p", "1p", "2p", "3p", "4p", "5p",
466+
"6p", "7p", "8p", "9p", "10p", "11p"
467+
]
468+
469+
let days = [
470+
"Saturday", "Friday", "Thursday",
471+
"Wednesday", "Tuesday", "Monday", "Sunday"
472+
]
473+
474+
// Raw data [dayIndex, hourIndex, commitCount]
475+
let rawData: [[Double]] = [[0, 0, 5], [0, 1, 1], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 2], [0, 12, 4], [0, 13, 1], [0, 14, 1], [0, 15, 3], [0, 16, 4], [0, 17, 6], [0, 18, 4], [0, 19, 4], [0, 20, 3], [0, 21, 3], [0, 22, 2], [0, 23, 5], [1, 0, 7], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [1, 6, 0], [1, 7, 0], [1, 8, 0], [1, 9, 0], [1, 10, 5], [1, 11, 2], [1, 12, 2], [1, 13, 6], [1, 14, 9], [1, 15, 11], [1, 16, 6], [1, 17, 7], [1, 18, 8], [1, 19, 12], [1, 20, 5], [1, 21, 5], [1, 22, 7], [1, 23, 2], [2, 0, 1], [2, 1, 1], [2, 2, 0], [2, 3, 0], [2, 4, 0], [2, 5, 0], [2, 6, 0], [2, 7, 0], [2, 8, 0], [2, 9, 0], [2, 10, 3], [2, 11, 2], [2, 12, 1], [2, 13, 9], [2, 14, 8], [2, 15, 10], [2, 16, 6], [2, 17, 5], [2, 18, 5], [2, 19, 5], [2, 20, 7], [2, 21, 4], [2, 22, 2], [2, 23, 4], [3, 0, 7], [3, 1, 3], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [3, 6, 0], [3, 7, 0], [3, 8, 1], [3, 9, 0], [3, 10, 5], [3, 11, 4], [3, 12, 7], [3, 13, 14], [3, 14, 13], [3, 15, 12], [3, 16, 9], [3, 17, 5], [3, 18, 5], [3, 19, 10], [3, 20, 6], [3, 21, 4], [3, 22, 4], [3, 23, 1], [4, 0, 1], [4, 1, 3], [4, 2, 0], [4, 3, 0], [4, 4, 0], [4, 5, 1], [4, 6, 0], [4, 7, 0], [4, 8, 0], [4, 9, 2], [4, 10, 4], [4, 11, 4], [4, 12, 2], [4, 13, 4], [4, 14, 4], [4, 15, 14], [4, 16, 12], [4, 17, 1], [4, 18, 8], [4, 19, 5], [4, 20, 3], [4, 21, 7], [4, 22, 3], [4, 23, 0], [5, 0, 2], [5, 1, 1], [5, 2, 0], [5, 3, 3], [5, 4, 0], [5, 5, 0], [5, 6, 0], [5, 7, 0], [5, 8, 2], [5, 9, 0], [5, 10, 4], [5, 11, 1], [5, 12, 5], [5, 13, 10], [5, 14, 5], [5, 15, 7], [5, 16, 11], [5, 17, 6], [5, 18, 0], [5, 19, 5], [5, 20, 3], [5, 21, 4], [5, 22, 2], [5, 23, 0], [6, 0, 1], [6, 1, 0], [6, 2, 0], [6, 3, 0], [6, 4, 0], [6, 5, 0], [6, 6, 0], [6, 7, 0], [6, 8, 0], [6, 9, 0], [6, 10, 1], [6, 11, 0], [6, 12, 2], [6, 13, 1], [6, 14, 3], [6, 15, 4], [6, 16, 0], [6, 17, 0], [6, 18, 0], [6, 19, 0], [6, 20, 1], [6, 21, 2], [6, 22, 2], [6, 23, 6]]
476+
477+
// Process data for Highcharts bubble series
478+
var maxZ: Double = 0
479+
let highchartsData = rawData
480+
.filter { $0[2] > 0 } // Filter out items with 0 commits
481+
.map { item -> [String: Any] in
482+
let dayIndex = Int(item[0])
483+
let hourIndex = Int(item[1])
484+
let commitCount = item[2]
485+
let zValue = commitCount * 2 // Scale commit count for bubble size
486+
if zValue > maxZ {
487+
maxZ = zValue
488+
}
489+
return [
490+
"x": hourIndex, // Angle axis category index (hour)
491+
"y": dayIndex, // Radius axis category index (day)
492+
"z": zValue, // Bubble size value
493+
// Store original data for easy access in tooltip
494+
"commitCount": commitCount,
495+
"dayName": days[dayIndex],
496+
"hourName": hours[hourIndex]
497+
]
498+
}
499+
500+
return AAOptions()
501+
.chart(AAChart()
502+
// .polar(true)
503+
.type(.bubble)
504+
// .marginBottom(100) // Optional: Adjust plot area size if needed
505+
)
506+
.colors([
507+
AAGradientColor.wroughtIron, // Default color for the series
508+
])
509+
.title(AATitle()
510+
.text("Punch Card of Github"))
511+
.pane(AAPane()
512+
.startAngle(0)
513+
.endAngle(360)
514+
// .size("85%") // Adjust size if needed
515+
)
516+
.legend(AALegend()
517+
.enabled(false)) // Disable legend for cleaner look
518+
.xAxis(AAXAxis() // Angle Axis (Hours)
519+
.categories(hours)
520+
.tickmarkPlacement(.on) // Place ticks on the category mark
521+
.lineWidth(0) // Hide axis line
522+
.gridLineWidth(1) // Show radial grid lines
523+
.labels(AALabels()
524+
// .rotation(0) // Default
525+
)
526+
.min(0) // Ensure axis starts at the first category
527+
.max(Double(hours.count - 1)) // Ensure axis ends at the last category
528+
)
529+
.yAxis(AAYAxis() // Radius Axis (Days)
530+
.categories(days)
531+
.reversed(false) // ECharts days[0] is outermost, Highcharts y=0 is center
532+
.min(0) // Ensure axis starts at the first category (center)
533+
.max(Double(days.count - 1)) // Ensure axis ends at the last category (outermost)
534+
.lineWidth(0) // Hide axis line
535+
// .gridLineInterpolation(.polygon) // Makes circular grid lines look better
536+
.gridLineWidth(0)
537+
.labels(AALabels()
538+
.rotation(-45) // Rotate labels like ECharts
539+
.align(.right)
540+
// .reserveSpace(true) // Important for rotated labels
541+
.style(AAStyle()
542+
// .textOverflow("none") // Prevent ellipsis if labels are long
543+
)
544+
.distance(5) // Adjust distance from axis if needed
545+
)
546+
// .tickPositions(days.indices.map { Double($0) }) // Optional: Define tick positions explicitly
547+
)
548+
.tooltip(AATooltip()
549+
.useHTML(true) // Allows richer HTML formatting if needed
550+
.headerFormat("") // No header needed as we format the whole thing
551+
.pointFormatter("""
552+
function () {
553+
return this.point.options.commitCount + ' commits in ' + this.point.options.hourName + ' of ' + this.point.options.dayName;
554+
}
555+
""") // Use the JS formatter function
556+
)
557+
.plotOptions(AAPlotOptions()
558+
.bubble(AABubble()
559+
.minSize(0) // Bubbles with z=0 will have 0 size
560+
.maxSize(max(maxZ, 20)) // Scale max size based on data, ensure minimum reasonable size
561+
// .zThreshold(0.1) // Values below this are treated as minSize
562+
.dataLabels(AADataLabels() // Hide data labels on bubbles
563+
.enabled(false))
564+
// .animation(AAAnimation().duration(1000)) // Optional animation duration
565+
)
566+
// .series(AASeries()
567+
// .clip(false) // Prevent bubbles near the edge from being clipped
568+
// )
569+
)
570+
.series([
571+
AASeriesElement()
572+
.name("Punch Card") // Name for the series
573+
.data(highchartsData) // Use the processed Swift data
574+
])
575+
}
576+
577+
// MARK: - Polar Bubble Chart (Punch Card)
578+
static func punchCardChart3() -> AAOptions {
579+
let aaOptions = punchCardChart2()
580+
aaOptions.chart?.polar = true
581+
582+
aaOptions.yAxis?.min = nil
583+
aaOptions.yAxis?.max = nil
584+
585+
return aaOptions
586+
}
314587
}

0 commit comments

Comments
 (0)