pict-section-activitygraph 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pict-section-activitygraph",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Pict section that renders a stacked-bar activity timeline: one bar per period, stacked by type or actor, across parallel domains. Themed SVG, no chart dependency.",
5
5
  "main": "source/Pict-Section-ActivityGraph.js",
6
6
  "scripts": {
@@ -73,6 +73,7 @@ function _colorFor(pStackBy, pKey, pIndex)
73
73
  /**
74
74
  * pModel:
75
75
  * { Period, StackBy ('ActivityType'|'ActorKind'), Measure ('EventCount'|'Volume'),
76
+ * LegendPosition ('top-left'|'top-right'|'bottom-left'|'bottom-right'|'none', default 'bottom-right'),
76
77
  * Columns: [ { Key, Title, Accent } ], Buckets: [ { PeriodStart, PeriodKey, Domain, ActivityType, ActorKind, EventCount, Volume } ] }
77
78
  */
78
79
  function buildSVG(pModel)
@@ -83,6 +84,8 @@ function buildSVG(pModel)
83
84
  let tmpMeasure = (tmpModel.Measure === 'Volume') ? 'Volume' : 'EventCount';
84
85
  let tmpColumns = Array.isArray(tmpModel.Columns) && tmpModel.Columns.length ? tmpModel.Columns : [{ Key: 'WorkItems', Title: 'Work Items', Accent: '#e0891e' }, { Key: 'Knowledge', Title: 'Knowledge', Accent: '#3f7fd0' }];
85
86
  let tmpBuckets = Array.isArray(tmpModel.Buckets) ? tmpModel.Buckets : [];
87
+ let tmpLegendOptions = { 'top-left': 1, 'top-right': 1, 'bottom-left': 1, 'bottom-right': 1, 'none': 1 };
88
+ let tmpLegendPosition = tmpLegendOptions[tmpModel.LegendPosition] ? tmpModel.LegendPosition : 'bottom-right';
86
89
 
87
90
  // --- geometry ---
88
91
  const WIDTH = 940;
@@ -139,26 +142,43 @@ function buildSVG(pModel)
139
142
  + '<text x="' + (WIDTH / 2) + '" y="' + (tmpH / 2) + '" text-anchor="middle" font-family="sans-serif" font-size="14" fill="' + _C.muted + '">No activity in this range yet.</text></svg>';
140
143
  }
141
144
 
142
- let tmpChartTop = TOP + LEGEND_H;
145
+ // Legend placement is configurable (default bottom-right, out of the title's way). When it is not at
146
+ // the top, the chart body starts higher and grows a band at the bottom to hold it.
147
+ let tmpLegendTop = tmpLegendPosition.indexOf('top') === 0;
148
+ let tmpLegendRight = tmpLegendPosition.indexOf('right') >= 0;
149
+ let tmpShowLegend = (tmpLegendPosition !== 'none') && (tmpStackKeys.length > 0);
150
+
151
+ let tmpChartTop = TOP + ((tmpShowLegend && tmpLegendTop) ? LEGEND_H : 10);
143
152
  const GROUP_H = 22; // a super-group (quarter/year) gets its own header band, so its label
144
153
  // never shares a line with the per-row date labels.
145
154
  // Count the super-group bands up front so the SVG height accounts for them.
146
155
  let tmpGroupCount = 0;
147
156
  let tmpScanGroup = null;
148
157
  tmpPeriodOrder.forEach((pRow) => { let tmpG = _superGroup(pRow.PeriodStart, tmpPeriod); if (tmpG && tmpG !== tmpScanGroup) { tmpGroupCount += 1; tmpScanGroup = tmpG; } });
149
- let tmpHeight = tmpChartTop + tmpPeriodOrder.length * ROW_H + tmpGroupCount * GROUP_H + 20;
158
+ let tmpBodyBottom = tmpChartTop + tmpPeriodOrder.length * ROW_H + tmpGroupCount * GROUP_H;
159
+ let tmpHeight = tmpBodyBottom + ((tmpShowLegend && !tmpLegendTop) ? LEGEND_H : 0) + 16;
150
160
  let tmpSVG = [];
151
161
  tmpSVG.push('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + WIDTH + ' ' + tmpHeight + '" width="100%" role="img" aria-label="System activity, one bar per ' + _esc(tmpPeriod) + ' stacked by ' + _esc(tmpStackBy) + ', each column scaled 0 to ' + tmpMaxTotal + '" font-family="-apple-system, Segoe UI, Roboto, sans-serif">');
152
162
 
153
- // legend
154
- let tmpLegendX = PAD_L;
163
+ // title (always top-left)
155
164
  tmpSVG.push('<text x="0" y="' + (TOP - 22) + '" font-size="15" font-weight="600" fill="' + _C.text + '">System Activity</text>');
156
- tmpStackKeys.forEach((pKey) =>
165
+
166
+ // legend: a swatch + label per stack key, placed per LegendPosition. The total width is measured
167
+ // first so a right-aligned legend ends flush with the chart's right edge.
168
+ if (tmpShowLegend)
157
169
  {
158
- tmpSVG.push('<rect x="' + tmpLegendX + '" y="' + (TOP - 4) + '" width="11" height="11" rx="2" fill="' + tmpColorByKey[pKey] + '"/>');
159
- tmpSVG.push('<text x="' + (tmpLegendX + 16) + '" y="' + (TOP + 5) + '" font-size="12" fill="' + _C.muted + '">' + _esc(pKey) + '</text>');
160
- tmpLegendX += 20 + _esc(pKey).length * 7.2 + 14;
161
- });
170
+ let tmpItems = tmpStackKeys.map((pKey) => ({ Key: pKey, Color: tmpColorByKey[pKey], W: 16 + _esc(pKey).length * 6.8 }));
171
+ let tmpTotalW = tmpItems.reduce((pSum, pItem) => pSum + pItem.W + 16, 0) - 16;
172
+ let tmpLX = tmpLegendRight ? (WIDTH - PAD_R - tmpTotalW) : PAD_L;
173
+ let tmpSwatchY = tmpLegendTop ? (TOP - 4) : (tmpBodyBottom + 14);
174
+ let tmpTextY = tmpLegendTop ? (TOP + 5) : (tmpBodyBottom + 23);
175
+ tmpItems.forEach((pItem) =>
176
+ {
177
+ tmpSVG.push('<rect x="' + tmpLX.toFixed(1) + '" y="' + tmpSwatchY + '" width="11" height="11" rx="2" fill="' + pItem.Color + '"/>');
178
+ tmpSVG.push('<text x="' + (tmpLX + 16).toFixed(1) + '" y="' + tmpTextY + '" font-size="12" fill="' + _C.muted + '">' + _esc(pItem.Key) + '</text>');
179
+ tmpLX += pItem.W + 16;
180
+ });
181
+ }
162
182
 
163
183
  // column headers: accent-colored title (left) + the axis scale (right). Both columns share one global
164
184
  // max, so labeling each track's full-width value with the same number makes the shared, comparable
@@ -25,6 +25,7 @@ const _DEFAULTS =
25
25
  Periods: ['Day', 'Week', 'Month', 'Quarter', 'Year'],
26
26
  StackBy: 'ActivityType',
27
27
  Measure: 'EventCount',
28
+ LegendPosition: 'bottom-right',
28
29
  Columns: [
29
30
  { Key: 'WorkItems', Title: 'Work Items', Accent: '#e0891e' },
30
31
  { Key: 'Knowledge', Title: 'Knowledge', Accent: '#3f7fd0' }
@@ -71,6 +72,7 @@ class PictViewActivityGraph extends libPictView
71
72
  Periods: Array.isArray(tmpConfig.Periods) ? tmpConfig.Periods : _DEFAULTS.Periods,
72
73
  StackBy: tmpConfig.StackBy || _DEFAULTS.StackBy,
73
74
  Measure: tmpConfig.Measure || _DEFAULTS.Measure,
75
+ LegendPosition: tmpConfig.LegendPosition || _DEFAULTS.LegendPosition,
74
76
  Columns: Array.isArray(tmpConfig.Columns) ? tmpConfig.Columns : _DEFAULTS.Columns,
75
77
  Buckets: []
76
78
  };
@@ -146,6 +148,7 @@ class PictViewActivityGraph extends libPictView
146
148
  Period: this._State.Period,
147
149
  StackBy: this._State.StackBy,
148
150
  Measure: this._State.Measure,
151
+ LegendPosition: this._State.LegendPosition,
149
152
  Columns: this._State.Columns,
150
153
  Buckets: this._State.Buckets
151
154
  });