• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CBIIT / bento-c3dc-frontend / 25557662953

08 May 2026 01:15PM UTC coverage: 0.112% (-0.04%) from 0.153%
25557662953

push

github

web-flow
Merge pull request #504 from CBIIT/C3DC-2146

C3DC 2146

6 of 6162 branches covered (0.1%)

Branch coverage included in aggregate %.

0 of 2386 new or added lines in 69 files covered. (0.0%)

31 existing lines in 7 files now uncovered.

10 of 8158 relevant lines covered (0.12%)

0.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/pages/CohortAnalyzer/HistogramPanel/chart/HistogramDatasetChart.js
NEW
1
import React from 'react';
×
NEW
2
import {
×
3
  BarChart,
4
  Bar,
5
  XAxis,
6
  YAxis,
7
  CartesianGrid,
8
  Tooltip,
9
  ResponsiveContainer,
10
  Cell,
11
  LineChart,
12
  Line,
13
  PieChart,
14
  Pie,
15
} from 'recharts';
NEW
16
import CustomChartTooltip from './CustomChartTooltip';
×
NEW
17
import CustomXAxisTick from './CustomXAxisTick';
×
NEW
18
import { barColors } from '../HistogramPanel.styled';
×
19

NEW
20
export const CHART_TYPE_KEYS = {
×
21
  PIE: 'pie',
22
  VERTICAL_BAR: 'verticalBar',
23
  HORIZONTAL_BAR: 'horizontalBar',
24
  LINE: 'line',
25
};
26

NEW
27
export const DEFAULT_CHART_TYPE = CHART_TYPE_KEYS.VERTICAL_BAR;
×
28

NEW
29
const tooltipBox = {
×
30
  backgroundColor: 'white',
31
  padding: '10px',
32
  border: '1px solid #ccc',
33
  borderRadius: '4px',
34
  boxShadow: 'none',
35
};
36

NEW
37
const formatCategoryLabel = (raw) => {
×
NEW
38
  const labelMap = { OtherFew: 'Other Few', OtherMany: 'Other Many' };
×
NEW
39
  return labelMap[raw] || raw;
×
40
};
41

NEW
42
const MultiseriesTooltip = ({ active, payload, label, viewType }) => {
×
NEW
43
  if (!active || !payload || !payload.length) return null;
×
NEW
44
  const isPercentage = viewType === 'percentage';
×
NEW
45
  return (
×
46
    <div style={tooltipBox}>
47
      <p style={{ margin: 0, fontFamily: 'Poppins', fontSize: '13px', color: '#000' }}>
48
        {formatCategoryLabel(label)}
49
      </p>
50
      {payload.map((p) => (
NEW
51
        <p key={String(p.dataKey)} style={{ margin: 0, fontFamily: 'Poppins', fontSize: '13px', color: '#000' }}>
×
52
          {p.name}: {Number(p.value).toFixed(1)}{isPercentage ? '%' : ''}
×
53
        </p>
54
      ))}
55
    </div>
56
  );
57
};
58

NEW
59
const PieChartTooltip = ({ active, payload, viewType }) => {
×
NEW
60
  if (!active || !payload || !payload.length) return null;
×
NEW
61
  const isPercentage = viewType === 'percentage';
×
NEW
62
  const p = payload[0];
×
NEW
63
  return (
×
64
    <div style={tooltipBox}>
65
      <p style={{ margin: 0, fontFamily: 'Poppins', fontSize: '13px', color: '#000' }}>{formatCategoryLabel(p.name)}</p>
66
      <p style={{ margin: 0, fontFamily: 'Poppins', fontSize: '13px', color: '#000' }}>
67
        {Number(p.value).toFixed(1)}{isPercentage ? '%' : ''}
×
68
      </p>
69
    </div>
70
  );
71
};
72

NEW
73
const yAxisTickStyle = {
×
74
  fontSize: 11,
75
  fill: '#666666',
76
  fontFamily: 'Nunito',
77
  fontWeight: 500,
78
  lineHeight: 11,
79
  letterSpacing: 0,
80
};
81

82
function buildPieData(rows) {
NEW
83
  return rows
×
84
    .map((entry) => {
NEW
85
      const va = Number(entry.valueA) || 0;
×
NEW
86
      const vb = Number(entry.valueB) || 0;
×
NEW
87
      const vc = Number(entry.valueC) || 0;
×
NEW
88
      const total = va + vb + vc;
×
89
      const fill =
NEW
90
        va >= vb && va >= vc ? entry.colorA : vb >= vc ? entry.colorB : entry.colorC;
×
NEW
91
      return { name: entry.name, value: total, fill };
×
92
    })
NEW
93
    .filter((d) => d.value > 0);
×
94
}
95

96
/**
97
 * Recharts views for a single histogram dataset. Uses a fixed height so layout stays stable when switching chart types.
98
 */
99
export function HistogramDatasetChart({
100
  rows,
101
  viewType,
102
  chartType,
103
  valueA,
104
  valueB,
105
  valueC,
106
  compact,
107
  height,
108
  width = '100%',
×
109
  estimatedChartWidth = 400,
×
110
  cellHover,
111
  handleMouseEnter,
112
  handleMouseLeave,
113
  xAxisHeight = 50,
×
114
  c1Name = 'Cohort A',
×
115
  c2Name = 'Cohort B',
×
116
  c3Name = 'Cohort C',
×
117
}) {
NEW
118
  const bottomMargin = compact ? 12 : 0;
×
NEW
119
  const yAxisTickFormatter = (value) => {
×
NEW
120
    const num = Number(value);
×
NEW
121
    const formatted = num % 1 === 0 ? num : num.toFixed(1);
×
NEW
122
    return viewType === 'percentage' ? `${formatted}%` : formatted;
×
123
  };
124

NEW
125
  const dataLength = (rows && rows.length) || 1;
×
NEW
126
  const availableWidth = (estimatedChartWidth / dataLength) * 0.9;
×
127

NEW
128
  const verticalBarMargin = {
×
129
    top: 20,
130
    right: 30,
131
    left: 10,
132
    bottom: bottomMargin,
133
  };
134

NEW
135
  const lineMargin = { ...verticalBarMargin };
×
136

NEW
137
  const horizontalBarMargin = {
×
138
    top: 12,
139
    right: 24,
140
    left: compact ? 18 : 18,
×
141
    bottom: 12,
142
  };
143

NEW
144
  const pieMargin = { top: 8, right: 8, left: 8, bottom: 8 };
×
145

NEW
146
  const xTickProps = {
×
147
    fontSize: compact ? 10 : 10,
×
148
    lineHeight: compact ? 10 : 11,
×
149
    letterSpacing: 0,
150
  };
151

NEW
152
  if (chartType === CHART_TYPE_KEYS.PIE) {
×
NEW
153
    const pieData = buildPieData(rows);
×
NEW
154
    if (pieData.length === 0) {
×
NEW
155
      return (
×
156
        <div
157
          style={{
158
            width: '100%',
159
            height,
160
            display: 'flex',
161
            alignItems: 'center',
162
            justifyContent: 'center',
163
            fontFamily: 'Poppins',
164
            fontSize: 13,
165
            color: '#666',
166
          }}
167
        >
168
          No data to display
169
        </div>
170
      );
171
    }
NEW
172
    return (
×
173
      <ResponsiveContainer width={width} height={height}>
174
        <PieChart margin={pieMargin}>
175
          <Pie
176
            data={pieData}
177
            dataKey="value"
178
            nameKey="name"
179
            cx="50%"
180
            cy="50%"
181
            outerRadius={Math.min(height, estimatedChartWidth) * 0.36}
182
            stroke="#000"
183
            strokeWidth={0.5}
184
          >
185
            {pieData.map((entry, i) => (
NEW
186
              <Cell key={`pie-${entry.name}-${i}`} fill={i === 0 ? barColors.colorA : i === 1 ? barColors.colorB : barColors.colorC} />
×
187
            ))}
188
          </Pie>
189
          <Tooltip content={<PieChartTooltip viewType={viewType} />} />
190
        </PieChart>
191
      </ResponsiveContainer>
192
    );
193
  }
194

NEW
195
  if (chartType === CHART_TYPE_KEYS.LINE) {
×
NEW
196
    return (
×
197
      <ResponsiveContainer width={width} height={height}>
198
        <LineChart data={rows} margin={lineMargin}>
199
          <CartesianGrid strokeDasharray="3 3" stroke="#e0e0e0" horizontal vertical={false} />
200
          <XAxis
201
            dataKey="name"
202
            interval={0}
203
            angle={0}
204
            textAnchor="middle"
205
            height={xAxisHeight}
206
            tick={(props) => (
NEW
207
              <CustomXAxisTick {...props} width={availableWidth} {...xTickProps} />
×
208
            )}
209
          />
210
          <YAxis
211
            domain={[0, 'dataMax']}
212
            tickFormatter={yAxisTickFormatter}
213
            tick={yAxisTickStyle}
214
          />
215
          <Tooltip content={<MultiseriesTooltip viewType={viewType} />} />
216
          {valueA > 0 && (
×
217
            <Line
218
              type="monotone"
219
              dataKey="valueA"
220
              name={c1Name}
221
              stroke={barColors.colorA}
222
              strokeWidth={2}
223
              dot={{ r: 3, strokeWidth: 1, stroke: '#333', fill: barColors.colorA }}
224
              activeDot={{ r: 4 }}
225
            />
226
          )}
227
          {valueB > 0 && (
×
228
            <Line
229
              type="monotone"
230
              dataKey="valueB"
231
              name={c2Name}
232
              stroke={barColors.colorB}
233
              strokeWidth={2}
234
              dot={{ r: 3, strokeWidth: 1, stroke: '#333', fill: barColors.colorB }}
235
              activeDot={{ r: 4 }}
236
            />
237
          )}
238
          {valueC > 0 && (
×
239
            <Line
240
              type="monotone"
241
              dataKey="valueC"
242
              name={c3Name}
243
              stroke={barColors.colorC}
244
              strokeWidth={2}
245
              dot={{ r: 3, strokeWidth: 1, stroke: '#333', fill: barColors.colorC }}
246
              activeDot={{ r: 4 }}
247
            />
248
          )}
249
        </LineChart>
250
      </ResponsiveContainer>
251
    );
252
  }
253

NEW
254
  if (chartType === CHART_TYPE_KEYS.HORIZONTAL_BAR) {
×
NEW
255
    return (
×
256
      <ResponsiveContainer width={width} height={height}>
257
        <BarChart layout="vertical" data={rows} margin={horizontalBarMargin}>
258
          <CartesianGrid strokeDasharray="3 3" stroke="#e0e0e0" horizontal vertical={false} />
259
          <XAxis type="number" domain={[0, 'dataMax']} tickFormatter={yAxisTickFormatter} tick={yAxisTickStyle} />
260
          <YAxis
261
            dataKey="name"
262
            type="category"
263
            width={compact ? 90 : 110}
×
264
            tick={{ fontSize: 10, fill: '#666666', fontFamily: 'Nunito' }}
NEW
265
            tickFormatter={(v) => formatCategoryLabel(v)}
×
266
          />
267
          <Tooltip content={<MultiseriesTooltip viewType={viewType} />} />
268
          {valueA > 0 && (
×
269
            <Bar dataKey="valueA" name="Cohort A" maxBarSize={28} stroke="#000" strokeWidth={0.6}>
270
              {rows.map((entry, entryIndex) => (
NEW
271
                <Cell
×
272
                  key={`hbar-a-${entryIndex}`}
273
                  fill={entry.colorA}
NEW
274
                  onMouseEnter={() => handleMouseEnter('valueA')}
×
275
                  onMouseLeave={handleMouseLeave}
276
                />
277
              ))}
278
            </Bar>
279
          )}
280
          {valueB > 0 && (
×
281
            <Bar dataKey="valueB" name="Cohort B" maxBarSize={28} stroke="#000" strokeWidth={0.6}>
282
              {rows.map((entry, entryIndex) => (
NEW
283
                <Cell
×
284
                  key={`hbar-b-${entryIndex}`}
285
                  fill={entry.colorB}
NEW
286
                  onMouseEnter={() => handleMouseEnter('valueB')}
×
287
                  onMouseLeave={handleMouseLeave}
288
                />
289
              ))}
290
            </Bar>
291
          )}
292
          {valueC > 0 && (
×
293
            <Bar dataKey="valueC" name="Cohort C" maxBarSize={28} stroke="#000" strokeWidth={0.6}>
294
              {rows.map((entry, entryIndex) => (
NEW
295
                <Cell
×
296
                  key={`hbar-c-${entryIndex}`}
297
                  fill={entry.colorC}
NEW
298
                  onMouseEnter={() => handleMouseEnter('valueC')}
×
299
                  onMouseLeave={handleMouseLeave}
300
                />
301
              ))}
302
            </Bar>
303
          )}
304
        </BarChart>
305
      </ResponsiveContainer>
306
    );
307
  }
308

309
  // verticalBar (default)
NEW
310
  return (
×
311
    <ResponsiveContainer width={width} height={height}>
312
      <BarChart data={rows} margin={verticalBarMargin}>
313
        <CartesianGrid strokeDasharray="3 3" stroke="#e0e0e0" horizontal vertical={false} />
314
        <XAxis
315
          dataKey="name"
316
          interval={0}
317
          angle={0}
318
          textAnchor="middle"
319
          height={xAxisHeight}
320
          tick={(props) => (
NEW
321
            <CustomXAxisTick {...props} width={availableWidth} {...xTickProps} />
×
322
          )}
323
        />
324
        <YAxis domain={[0, 'dataMax']} tickFormatter={yAxisTickFormatter} tick={yAxisTickStyle} />
325
        <Tooltip content={<CustomChartTooltip viewType={viewType} cellHoverRef={cellHover} />} />
326
        {valueA > 0 && (
×
327
          <Bar dataKey="valueA" maxBarSize={60} stroke="#000" strokeWidth={0.6}>
328
            {rows.map((entry, entryIndex) => (
NEW
329
              <Cell
×
330
                key={`vbar-a-${entryIndex}`}
331
                fill={entry.colorA}
NEW
332
                onMouseEnter={() => handleMouseEnter('valueA')}
×
333
                onMouseLeave={handleMouseLeave}
334
              />
335
            ))}
336
          </Bar>
337
        )}
338
        {valueB > 0 && (
×
339
          <Bar dataKey="valueB" maxBarSize={60} stroke="#000" strokeWidth={0.6}>
340
            {rows.map((entry, entryIndex) => (
NEW
341
              <Cell
×
342
                key={`vbar-b-${entryIndex}`}
343
                fill={entry.colorB}
NEW
344
                onMouseEnter={() => handleMouseEnter('valueB')}
×
345
                onMouseLeave={handleMouseLeave}
346
              />
347
            ))}
348
          </Bar>
349
        )}
350
        {valueC > 0 && (
×
351
          <Bar dataKey="valueC" maxBarSize={60} stroke="#000" strokeWidth={0.6}>
352
            {rows.map((entry, entryIndex) => (
NEW
353
              <Cell
×
354
                key={`vbar-c-${entryIndex}`}
355
                fill={entry.colorC}
NEW
356
                onMouseEnter={() => handleMouseEnter('valueC')}
×
357
                onMouseLeave={handleMouseLeave}
358
              />
359
            ))}
360
          </Bar>
361
        )}
362
      </BarChart>
363
    </ResponsiveContainer>
364
  );
365
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc