@@ -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