@@ -53,6 +53,27 @@ class MathHelpers {
53
53
{
54
54
return ( offset + Date . now ( ) / ( cycleLengthMs || 2000 ) ) % 1 ;
55
55
}
56
+
57
+ static cheapHash ( s )
58
+ {
59
+ let hash = 0 , i = 0 , len = s . length ;
60
+ while ( i < len )
61
+ hash = ( ( hash << 5 ) - hash + s . charCodeAt ( i ++ ) ) << 0 ;
62
+
63
+ return hash + 2147483647 + 1 ;
64
+ }
65
+
66
+ // JavaScripts % operator is remainder, not modulo.
67
+ static modulo ( dividend , divisor )
68
+ {
69
+ const quotient = Math . floor ( dividend / divisor ) ;
70
+ return dividend - divisor * quotient ;
71
+ }
72
+
73
+ static normalizeRadians ( radians )
74
+ {
75
+ return MathHelpers . modulo ( radians , Math . PI * 2 ) ;
76
+ }
56
77
}
57
78
58
79
class ItemData {
@@ -61,8 +82,8 @@ class ItemData {
61
82
this . deptNumber = deptNumber ;
62
83
this . label = label ;
63
84
this . imageURL = imageURL ;
64
-
65
- this . hueOffset = MathHelpers . random ( 0.1 , 0.2 ) ;
85
+
86
+ this . hueOffset = MathHelpers . cheapHash ( label ) / 0xFFFFFFFF ;
66
87
this . colorLightness = MathHelpers . random ( 0.5 , 0.7 ) ;
67
88
this . colorSaturation = MathHelpers . random ( 0.2 , 0.5 ) ;
68
89
}
@@ -178,7 +199,7 @@ class RadialChart {
178
199
179
200
draw ( ctx )
180
201
{
181
- this . numSpokes = this . _complexity - 1 ;
202
+ this . numSpokes = this . _complexity ;
182
203
this . wedgeAngleRadians = TwoPI / this . numSpokes ;
183
204
this . angleOffsetRadians = Math . PI / 2 ; // Start at the top, rather than the right.
184
205
@@ -268,45 +289,94 @@ class RadialChart {
268
289
269
290
#drawWedgeLabels( ctx , index , instance )
270
291
{
271
- const midAngleRadians = this . #wedgeStartAngle( index ) + 0.5 * this . wedgeAngleRadians ;
292
+ const midAngleRadians = MathHelpers . normalizeRadians ( this . #wedgeStartAngle( index ) + 0.5 * this . wedgeAngleRadians ) ;
272
293
273
- const textInset = 15 ;
294
+ const textInset = - 15 ;
274
295
const textCenterPoint = this . center . add ( GeometryHelpers . createPointOnCircle ( midAngleRadians , this . innerRadius - textInset ) ) ;
275
296
276
297
const labelAngle = midAngleRadians + Math . PI / 2 ;
277
298
278
- ctx . save ( ) ;
279
- ctx . font = '12px sans-serif' ;
280
- ctx . fillStyle = 'black' ;
281
-
282
- ctx . translate ( textCenterPoint . x , textCenterPoint . y ) ;
283
- ctx . rotate ( labelAngle ) ;
299
+ {
300
+ ctx . save ( ) ;
301
+ ctx . font = '12px "Helvetica Neue", Helvetica, sans-serif' ;
302
+
303
+ // Numbers on inner ring.
304
+ ctx . translate ( textCenterPoint . x , textCenterPoint . y ) ;
305
+ ctx . rotate ( labelAngle ) ;
284
306
285
- const textSize = ctx . measureText ( instance . deptNumber ) ;
286
- ctx . fillText ( instance . deptNumber , - textSize . width / 2 , 0 ) ;
287
- ctx . restore ( ) ;
307
+ const textSize = ctx . measureText ( instance . deptNumber ) ;
308
+
309
+ ctx . strokeStyle = 'black' ;
310
+ ctx . lineWidth = 1 ;
311
+ ctx . strokeText ( instance . deptNumber , - textSize . width / 2 , 0 ) ;
312
+
313
+ {
314
+ ctx . save ( ) ;
315
+ ctx . shadowColor = "rgba(0, 0, 0, 0.5)" ;
316
+ ctx . shadowBlur = 5 ;
317
+ ctx . fillStyle = 'white' ;
318
+ ctx . fillText ( instance . deptNumber , - textSize . width / 2 , 0 ) ;
319
+ ctx . restore ( ) ;
320
+ }
321
+
322
+ ctx . restore ( ) ;
323
+ }
324
+
325
+ // Labels around outside.
326
+ const labelDistance = 20 ;
327
+ const labelHorizontalOffset = 60 ;
328
+ const outsideMidSegmentPoint = this . center . add ( GeometryHelpers . createPointOnCircle ( midAngleRadians , this . outerRadius + labelDistance ) ) ;
329
+ let outerLabelLocation = outsideMidSegmentPoint ;
330
+ const isRightSide = midAngleRadians < Math . PI / 2 || midAngleRadians > Math . PI * 1.5 ;
331
+ if ( isRightSide )
332
+ outerLabelLocation = outsideMidSegmentPoint . add ( new Point ( labelHorizontalOffset , 0 ) ) ;
333
+ else
334
+ outerLabelLocation = outsideMidSegmentPoint . add ( new Point ( - labelHorizontalOffset , 0 ) ) ;
335
+
336
+ {
337
+ ctx . save ( ) ;
338
+
339
+ ctx . translate ( outerLabelLocation . x , outerLabelLocation . y ) ;
340
+
341
+ ctx . font = '12px "Helvetica Neue", Helvetica, sans-serif' ;
342
+ ctx . fillStyle = 'black' ;
288
343
289
- const textLabelGap = 5 ;
290
- const outerLabelLocation = this . center . add ( GeometryHelpers . createPointOnCircle ( midAngleRadians , this . outerRadius + textLabelGap ) ) ;
344
+ let textOffset = 0 ;
345
+ if ( ! isRightSide )
346
+ textOffset = - ctx . measureText ( instance . label ) . width ;
347
+
348
+ ctx . fillText ( instance . label , textOffset , 0 ) ;
349
+ ctx . restore ( ) ;
350
+ }
291
351
292
- ctx . save ( ) ;
352
+ const wedgeArrowEnd = this . center . add ( GeometryHelpers . createPointOnCircle ( midAngleRadians , this . outerRadius ) ) ;
353
+ const wedgeArrowEndAngle = MathHelpers . normalizeRadians ( midAngleRadians + Math . PI ) ;
354
+ const arrowPath = this . #pathForArrow( outerLabelLocation , wedgeArrowEnd , wedgeArrowEndAngle ) ;
355
+
356
+ // Arrow.
357
+ {
358
+ ctx . save ( ) ;
359
+ ctx . strokeStyle = 'gray' ;
360
+ ctx . setLineDash ( [ 4 , 2 ] ) ;
361
+ ctx . stroke ( arrowPath ) ;
362
+ ctx . restore ( ) ;
363
+ }
293
364
294
- ctx . translate ( outerLabelLocation . x , outerLabelLocation . y ) ;
295
- ctx . rotate ( midAngleRadians + Math . PI / 2 - 0.5 ) ;
365
+ // Arrowhead.
366
+ {
367
+ ctx . save ( ) ;
368
+ const arrowheadPath = this . #pathForArrowHead( ) ;
296
369
297
- ctx . font = '12px sans-serif' ;
298
- ctx . fillStyle = 'black' ;
299
- ctx . fillText ( instance . label , 0 , 0 ) ;
300
- ctx . restore ( ) ;
301
-
302
- // const wedgeArrowEnd = this.center.add(GeometryHelpers.createPointOnCircle(midAngleRadians, this.outerRadius));
303
- // const arrowPath = this.#pathForArrow(outerLabelLocation, wedgeArrowEnd);
304
- //
305
- // ctx.save();
306
- // ctx.strokeStyle = 'gray';
307
- // ctx.setLineDash([10, 4]);
308
- // ctx.stroke(arrowPath);
309
- // ctx.restore();
370
+ ctx . translate ( wedgeArrowEnd . x , wedgeArrowEnd . y ) ;
371
+ const arrowheadSize = 12 ;
372
+ ctx . scale ( arrowheadSize , arrowheadSize ) ;
373
+ ctx . rotate ( midAngleRadians ) ;
374
+
375
+ ctx . fillStyle = 'gray' ;
376
+ ctx . fill ( arrowheadPath ) ;
377
+
378
+ ctx . restore ( ) ;
379
+ }
310
380
}
311
381
312
382
#drawBadge( ctx , index , instance )
@@ -325,7 +395,6 @@ class RadialChart {
325
395
ctx . translate ( imageCenterPoint . x , imageCenterPoint . y ) ;
326
396
ctx . rotate ( imageAngle ) ;
327
397
328
- // FIXME: This shadow makes Safari very slow.
329
398
ctx . shadowColor = "black" ;
330
399
ctx . shadowBlur = 5 ;
331
400
@@ -360,23 +429,39 @@ class RadialChart {
360
429
}
361
430
}
362
431
363
- // Unused
364
- #pathForArrow( startPoint , endPoint )
432
+ #pathForArrow( startPoint , endPoint , endAngle )
365
433
{
366
434
const arrowPath = new Path2D ( ) ;
367
435
arrowPath . moveTo ( startPoint . x , startPoint . y ) ;
368
436
// Compute a bezier path that keeps the line horizontal at the start and end.
437
+
438
+ const distance = startPoint . subtract ( endPoint ) . length ( ) ;
369
439
370
440
const controlPointProportion = 0.5 ;
371
-
372
441
const controlPoint1 = startPoint . add ( { x : controlPointProportion * ( endPoint . x - startPoint . x ) , y : 0 } ) ;
373
- const controlPoint2 = endPoint . subtract ( { x : controlPointProportion * ( endPoint . x - startPoint . x ) , y : 0 } ) ;
374
- arrowPath . bezierCurveTo ( controlPoint1 . x , controlPoint1 . y , controlPoint2 . x , controlPoint2 . y , endPoint . x , endPoint . y ) ;
442
+
443
+ const controlPoint2Offset = new Point ( controlPointProportion * distance * Math . cos ( endAngle ) , controlPointProportion * distance * Math . sin ( endAngle ) ) ;
444
+ const controlPoint2 = endPoint . subtract ( controlPoint2Offset ) ;
375
445
376
- //arrowPath.lineTo(endPoint.x, endPoint.y);
377
- // Add arrowhead
446
+ arrowPath . bezierCurveTo ( controlPoint1 . x , controlPoint1 . y , controlPoint2 . x , controlPoint2 . y , endPoint . x , endPoint . y ) ;
378
447
return arrowPath ;
379
448
}
449
+
450
+ #pathForArrowHead( )
451
+ {
452
+ // Arrowhead points left.
453
+ const arrowHeadPath = new Path2D ( ) ;
454
+ const pointyness = 0.5 ;
455
+ const breadth = 0.4 ;
456
+
457
+ arrowHeadPath . moveTo ( 0 , 0 ) ;
458
+ arrowHeadPath . quadraticCurveTo ( pointyness , 0 , 1 , breadth ) ;
459
+ arrowHeadPath . lineTo ( 1 , - breadth ) ;
460
+ arrowHeadPath . quadraticCurveTo ( pointyness , 0 , 0 , 0 ) ;
461
+ arrowHeadPath . closePath ( ) ;
462
+
463
+ return arrowHeadPath ;
464
+ }
380
465
}
381
466
382
467
class RadialChartStage extends Stage {
@@ -535,14 +620,14 @@ window.benchmarkClass = RadialChartBenchmark;
535
620
class FakeController {
536
621
constructor ( )
537
622
{
538
- this . initialComplexity = 102 ;
623
+ this . initialComplexity = 200 ;
539
624
this . startTime = new Date ;
540
625
}
541
626
542
627
shouldStop ( )
543
628
{
544
629
const now = new Date ( ) ;
545
- return ( now - this . startTime ) > 15000 ;
630
+ return ( now - this . startTime ) > 500 ;
546
631
}
547
632
548
633
results ( )
0 commit comments