Skip to content

Commit 740b67b

Browse files
committed
add shadow and TextIfFits in charting
1 parent 48615ea commit 740b67b

19 files changed

+536
-530
lines changed

Signum.React.Extensions/Chart/Chart.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ body.rtl .sf-chart-token .sf-query-token {
137137
}
138138

139139
.sf-chart-animable .sf-transition {
140-
transition: all 0.3s cubic-bezier(0.92, 0.02, 0.21, 1.01)
140+
transition: all 0.3s cubic-bezier(0.92, 0.02, 0.21, 1.01), filter 0.1s linear
141+
}
142+
143+
.shadow-group:hover .shadow {
144+
filter: drop-shadow( 1px 1px 3px rgba(0, 0, 0, .4));
141145
}
142146

143147
text.sf-initial-message {

Signum.React.Extensions/Chart/D3Scripts/Bars.tsx

Lines changed: 75 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import * as ChartClient from '../ChartClient';
44
import * as ChartUtils from './Components/ChartUtils';
55
import { translate, scale, rotate, skewX, skewY, matrix, scaleFor } from './Components/ChartUtils';
66
import { ChartRow, ChartScriptProps } from '../ChartClient';
7-
import TextEllipsis from './Components/TextEllipsis';
87
import { XKeyTicks, YScaleTicks, YKeyTicks, XScaleTicks } from './Components/Ticks';
98
import { XAxis, YAxis } from './Components/Axis';
109
import { Rule } from './Components/Rule';
1110
import InitialMessage from './Components/InitialMessage';
11+
import TextIfFits from './Components/TextIfFits';
12+
import TextEllipsis from './Components/TextEllipsis';
1213

1314

1415
export default function renderBars({ data, width, height, parameters, loading, onDrillDown, initialLoad, chartRequest, memo, dashboardFilter }: ChartScriptProps): React.ReactElement<any> {
@@ -70,6 +71,8 @@ export default function renderBars({ data, width, height, parameters, loading, o
7071

7172
var detector = dashboardFilter?.getActiveDetector(chartRequest);
7273

74+
const bandMargin = y.bandwidth() > 20 ? 2 : 0;
75+
7376
return (
7477
<svg direction="ltr" width={width} height={height}>
7578
<g opacity={dashboardFilter ? .5 : undefined}>
@@ -79,98 +82,85 @@ export default function renderBars({ data, width, height, parameters, loading, o
7982

8083
{/*PAINT GRAPH*/}
8184
<g className="shape" transform={translate(xRule.start('content'), yRule.start('content'))}>
82-
{orderedRows.map(r => {
85+
{keyValues.map(k => {
8386

84-
var active = detector?.(r);
87+
var key = keyColumn.getKey(k);
8588

86-
var key = keyColumn.getValueKey(r);
89+
var row: ChartRow | undefined = rowsByKey[key];
90+
var active = detector?.(row);
8791

88-
return (
89-
<rect key={key} className="shape sf-transition"
90-
opacity={active == false ? .5 : undefined}
91-
stroke={active == true ? "black" : y.bandwidth() > 4 ? '#fff' : undefined}
92-
strokeWidth={active == true ? 3 : undefined}
93-
transform={translate(0, y(key)!) + (initialLoad ? scale(0, 1) : scale(1, 1))}
94-
width={x(valueColumn.getValue(r))}
95-
height={y.bandwidth()}
96-
fill={keyColumn.getValueColor(r) ?? color(key)}
97-
onClick={e => onDrillDown(r, e)}
98-
cursor="pointer">
99-
<title>
100-
{keyColumn.getValueNiceName(r) + ': ' + valueColumn.getValueNiceName(r)}
101-
</title>
102-
</rect>
103-
);
104-
})}
105-
</g>
92+
var posx = x(row ? valueColumn.getValue(row) : 0)!;
10693

107-
{y.bandwidth() > 15 &&
108-
(isMargin ?
109-
<g className="y-label" transform={translate(xRule.end('labels'), yRule.start('content') + y.bandwidth() / 2)}>
110-
{(isAll ? keyValues : orderedRows.map(r => keyColumn.getValue(r))).map(k => <TextEllipsis key={keyColumn.getKey(k)}
111-
transform={translate(0, y(keyColumn.getKey(k))!)}
112-
maxWidth={xRule.size('labels')}
113-
padding={labelMargin}
114-
className="y-label sf-transition"
115-
fill={(keyColumn.getColor(k) ?? color(keyColumn.getKey(k)))}
116-
dominantBaseline="middle"
117-
textAnchor="end"
118-
fontWeight="bold"
119-
onClick={e => onDrillDown({ c0: k }, e)}
120-
cursor="pointer">
121-
{keyColumn.getNiceName(k)}
122-
</TextEllipsis>)}
123-
</g> :
124-
isInside ?
125-
<g className="y-label" transform={translate(xRule.start('content') + labelMargin, yRule.start('content') + y.bandwidth() / 2)}>
126-
{(isAll ? keyValues : orderedRows.map(r => keyColumn.getValue(r))).map(k => {
127-
128-
var row = rowsByKey[keyColumn.getKey(k)];
129-
130-
var posx = x(row ? valueColumn.getValue(row) : 0)!;
131-
return (
132-
<TextEllipsis key={keyColumn.getKey(k)}
133-
transform={translate(posx >= size / 2 ? 0 : posx, y(keyColumn.getKey(k))!)}
134-
maxWidth={posx >= size / 2 ? posx : size - posx}
94+
return (
95+
<g className="shadow-group" key={key}>
96+
{row && <rect className="shape sf-transition shadow"
97+
opacity={active == false ? .5 : undefined}
98+
transform={translate(0, y(key)! + bandMargin) + (initialLoad ? scale(0, 1) : scale(1, 1))}
99+
width={x(valueColumn.getValue(row))}
100+
height={y.bandwidth() - bandMargin * 2}
101+
fill={keyColumn.getValueColor(row) ?? color(key)}
102+
onClick={e => onDrillDown(row!, e)}
103+
cursor="pointer">
104+
<title>
105+
{keyColumn.getValueNiceName(row) + ': ' + valueColumn.getValueNiceName(row)}
106+
</title>
107+
</rect>
108+
}
109+
{y.bandwidth() > 15 && (isAll || row != null) &&
110+
(isMargin ?
111+
<g className="y-label" transform={translate(-labelMargin, y.bandwidth() / 2)}>
112+
<TextEllipsis
113+
transform={translate(0, y(keyColumn.getKey(key))!)}
114+
maxWidth={xRule.size('labels')}
115+
padding={labelMargin}
116+
className="y-label sf-transition"
117+
fill={(keyColumn.getColor(key) ?? color(keyColumn.getKey(key)))}
118+
dominantBaseline="middle"
119+
textAnchor="end"
120+
fontWeight="bold"
121+
onClick={e => onDrillDown({ c0: key }, e)}
122+
cursor="pointer">
123+
{keyColumn.getNiceName(key)}
124+
</TextEllipsis>)
125+
</g> :
126+
isInside ?
127+
<g className="y-label" transform={translate(labelMargin, y.bandwidth() / 2)}>
128+
<TextEllipsis
129+
transform={translate(posx >= size / 2 ? 0 : posx, y(keyColumn.getKey(key))!)}
130+
maxWidth={posx >= size / 2 ? posx : size - posx}
131+
padding={labelMargin}
132+
className="y-label sf-transition"
133+
fill={posx >= size / 2 ? '#fff' : (keyColumn.getColor(key) ?? color(keyColumn.getKey(key)))}
134+
dominantBaseline="middle"
135+
fontWeight="bold"
136+
onClick={e => onDrillDown({ c0: key }, e)}
137+
cursor="pointer">
138+
{keyColumn.getNiceName(key)}
139+
</TextEllipsis>
140+
</g> : null
141+
)}
142+
{y.bandwidth() > 15 && parseFloat(parameters["NumberOpacity"]) > 0 && row &&
143+
<g className="numbers-label">
144+
<TextIfFits
145+
transform={translate(x(valueColumn.getValue(row))! / 2, y(keyColumn.getValueKey(row))! + y.bandwidth() / 2)}
146+
maxWidth={x(valueColumn.getValue(row))!}
135147
padding={labelMargin}
136-
className="y-label sf-transition"
137-
fill={posx >= size / 2 ? '#fff' : (keyColumn.getColor(k) ?? color(keyColumn.getKey(k)))}
148+
className="number-label sf-transition"
149+
fill={parameters["NumberColor"] ?? "#000"}
138150
dominantBaseline="middle"
151+
opacity={parameters["NumberOpacity"]}
152+
textAnchor="middle"
139153
fontWeight="bold"
140-
onClick={e => onDrillDown({ c0: k }, e)}
154+
onClick={e => onDrillDown(row!, e)}
141155
cursor="pointer">
142-
{keyColumn.getNiceName(k)}
143-
</TextEllipsis>
144-
);
145-
})}
146-
</g> : null
147-
)}
148-
149-
{y.bandwidth() > 15 && parseFloat(parameters["NumberOpacity"]) > 0 &&
150-
<g className="numbers-label" transform={translate(xRule.start('content'), yRule.start('content'))}>
151-
{orderedRows
152-
.filter(r => x(valueColumn.getValue(r))! > 20)
153-
.map(r => {
154-
var posx = x(valueColumn.getValue(r))!;
155-
156-
return (<TextEllipsis key={keyColumn.getValueKey(r)}
157-
transform={translate(x(valueColumn.getValue(r))! / 2, y(keyColumn.getValueKey(r))! + y.bandwidth() / 2)}
158-
maxWidth={posx >= size / 2 ? posx : size - posx}
159-
padding={labelMargin}
160-
className="number-label sf-transition"
161-
fill={parameters["NumberColor"] ?? "#000"}
162-
dominantBaseline="middle"
163-
opacity={parameters["NumberOpacity"]}
164-
textAnchor="middle"
165-
fontWeight="bold"
166-
onClick={e => onDrillDown(r, e)}
167-
cursor="pointer">
168-
{valueColumn.getValueNiceName(r)}
169-
</TextEllipsis>);
170-
})}
171-
</g>
172-
}
173-
156+
{valueColumn.getValueNiceName(row)}
157+
</TextIfFits>
158+
</g>
159+
}
160+
</g>
161+
);
162+
})}
163+
</g>
174164
<InitialMessage data={data} x={xRule.middle("content")} y={yRule.middle("content")} loading={loading} />
175165
<g opacity={dashboardFilter ? .5 : undefined}>
176166
<XAxis xRule={xRule} yRule={yRule} />

Signum.React.Extensions/Chart/D3Scripts/BubblePack.tsx

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -83,40 +83,40 @@ export default function renderBubblePack({ data, width, height, parameters, load
8383
nodes.orderByDescending(a => a.r).map(d => {
8484
const active = activeDetector?.(isFolder(d.data) ? ({ c2: d.data.folder }) : d.data);
8585
return (
86-
<g key={getNodeKey(d)} className="node sf-transition" transform={translate(d.x, d.y) + (initialLoad ? scale(0, 0) : scale(1, 1))} cursor="pointer"
87-
onClick={e => isFolder(d.data) ? onDrillDown({ c2: d.data.folder }, e) : onDrillDown(d.data, e)}>
88-
<circle className="sf-transition" shapeRendering="initial" r={d.r}
89-
opacity={active == false ? .5 : undefined}
90-
fill={isFolder(d.data) ? folderColor!(d.data.folder) : color(d.data)!}
91-
fillOpacity={parameters["FillOpacity"] ?? undefined}
92-
stroke={active == true ? "black" : parameters["StrokeColor"] ?? (isFolder(d.data) ? folderColor!(d.data.folder) : (color(d.data) ?? undefined))}
93-
strokeWidth={parameters["StrokeWidth"]} strokeOpacity={1} />
94-
{!isFolder(d.data) &&
95-
<TextEllipsis maxWidth={d.r * 2} padding={1} etcText=""
96-
dominantBaseline="middle" textAnchor="middle" dy={showNumber && d.r > numberSizeLimit ? "-0.5em" : undefined}>
97-
{keyColumn.getValueNiceName(d.data as ChartRow)}
98-
</TextEllipsis>
99-
}
100-
{showNumber && d.r > numberSizeLimit && !isFolder(d.data) &&
101-
<text fill={parameters["NumberColor"] ?? "#000"}
102-
dominantBaseline="middle"
103-
textAnchor="middle"
104-
fontWeight="bold"
105-
opacity={parseFloat(parameters["NumberOpacity"]) * d.r / 30}
106-
dy=".5em">
107-
{valueColumn.getValueNiceName(d.data as ChartRow)}
108-
</text>
109-
}
110-
<title>
111-
{isFolder(d.data) ? parentColumn!.getNiceName(d.data.folder) :
112-
(keyColumn.getValueNiceName(d.data as ChartRow) + (parentColumn == null ? '' : (' (' + parentColumn.getValueNiceName(d.data as ChartRow) + ')')))}:
113-
{isFolder(d.data) ? format(size.invert(d.value!)) :
114-
(valueColumn.getValueNiceName(d.data)
115-
+ (colorScaleColumn == null ? '' : (' (' + colorScaleColumn.getValueNiceName(d.data) + ')'))
116-
+ (colorSchemeColumn == null ? '' : (' (' + colorSchemeColumn.getValueNiceName(d.data) + ')'))
117-
)}
118-
</title>
119-
</g>);
86+
<g key={getNodeKey(d)} className="node sf-transition shadow-group" transform={translate(d.x, d.y) + (initialLoad ? scale(0, 0) : scale(1, 1))} cursor="pointer"
87+
onClick={e => isFolder(d.data) ? onDrillDown({ c2: d.data.folder }, e) : onDrillDown(d.data, e)}>
88+
<circle className="sf-transition shadow" shapeRendering="initial" r={d.r}
89+
opacity={active == false ? .5 : undefined}
90+
fill={isFolder(d.data) ? folderColor!(d.data.folder) : color(d.data)!}
91+
fillOpacity={parameters["FillOpacity"] ?? undefined}
92+
stroke={active == true ? "black" : parameters["StrokeColor"] ?? (isFolder(d.data) ? folderColor!(d.data.folder) : (color(d.data) ?? undefined))}
93+
strokeWidth={parameters["StrokeWidth"]} strokeOpacity={1} />
94+
{!isFolder(d.data) &&
95+
<TextEllipsis maxWidth={d.r * 2} padding={1} etcText=""
96+
dominantBaseline="middle" textAnchor="middle" dy={showNumber && d.r > numberSizeLimit ? "-0.5em" : undefined}>
97+
{keyColumn.getValueNiceName(d.data as ChartRow)}
98+
</TextEllipsis>
99+
}
100+
{showNumber && d.r > numberSizeLimit && !isFolder(d.data) &&
101+
<text fill={parameters["NumberColor"] ?? "#000"}
102+
dominantBaseline="middle"
103+
textAnchor="middle"
104+
fontWeight="bold"
105+
opacity={parseFloat(parameters["NumberOpacity"]) * d.r / 30}
106+
dy=".5em">
107+
{valueColumn.getValueNiceName(d.data as ChartRow)}
108+
</text>
109+
}
110+
<title>
111+
{isFolder(d.data) ? parentColumn!.getNiceName(d.data.folder) :
112+
(keyColumn.getValueNiceName(d.data as ChartRow) + (parentColumn == null ? '' : (' (' + parentColumn.getValueNiceName(d.data as ChartRow) + ')')))}:
113+
{isFolder(d.data) ? format(size.invert(d.value!)) :
114+
(valueColumn.getValueNiceName(d.data)
115+
+ (colorScaleColumn == null ? '' : (' (' + colorScaleColumn.getValueNiceName(d.data) + ')'))
116+
+ (colorSchemeColumn == null ? '' : (' (' + colorSchemeColumn.getValueNiceName(d.data) + ')'))
117+
)}
118+
</title>
119+
</g>);
120120
})
121121
}
122122
<InitialMessage data={data} x={width / 2} y={height / 2} loading={loading} />

Signum.React.Extensions/Chart/D3Scripts/Bubbleplot.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,13 @@ export default function renderBubbleplot({ data, width, height, parameters, load
9595

9696
return (
9797
<g key={keyColumns.map(c => c.getValueKey(r)).join("/")}
98-
className="shape-serie sf-transition"
98+
className="shape-serie sf-transition shadow-group"
9999
opacity={active == false ? .5 : undefined}
100100
transform={translate(x(horizontalColumn.getValue(r))!, -y(verticalColumn.getValue(r))!) + (initialLoad ? scale(0, 0) : scale(1, 1))}
101101
cursor="pointer"
102102
onClick={e => onDrillDown(r, e)}
103103
>
104-
<circle className="shape sf-transition"
104+
<circle className="shape sf-transition shadow"
105105
stroke={active == true ? "black" : colorKeyColumn.getValueColor(r) ?? color(r)}
106106
strokeWidth={3} fill={colorKeyColumn.getValueColor(r) ?? color(r)}
107107
fillOpacity={parseFloat(parameters["FillOpacity"])}

Signum.React.Extensions/Chart/D3Scripts/CalendarStream.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -231,26 +231,30 @@ export function CalendarYear({ year, rules, rowByDate, width, height, onDrillDow
231231
</g>
232232

233233
<g transform={translate(rules.daysRule.start("daysContent"), rules.weeksRule.start("weeksContent"))}>
234-
{d3.utcDays(new Date(Date.UTC(year, 0, 1)), new Date(Date.UTC(year + 1, 0, 1))).map(d => {
235-
const r: ChartRow | undefined = rowByDate[cleanDate(d)];
236-
const active = r && detector?.(r);
237-
return <rect key={d.toISOString()}
238-
className="sf-transition"
239-
opacity={active == false ? .5 : undefined}
240-
stroke={active == true ? "black" : "#ccc"}
241-
strokeWidth={active == true ? 2 : undefined}
242-
fill={r == undefined || initialLoad ? "#fff" : color(r)}
243-
width={cellSize}
244-
height={cellSize}
245-
x={day(d) * cellSize}
246-
y={week(d) * cellSize}
247-
cursor="pointer"
248-
onClick={e => r == undefined ? null : onDrillDown(r, e)}>
249-
<title>
250-
{dateFormat(d) + (r == undefined ? "" : ("(" + valueColumn.getValueNiceName(r) + ")"))}
251-
</title>
252-
</rect>
253-
})}
234+
{d3.utcDays(new Date(Date.UTC(year, 0, 1)), new Date(Date.UTC(year + 1, 0, 1))).map(d => {
235+
const r: ChartRow | undefined = rowByDate[cleanDate(d)];
236+
const active = r && detector?.(r);
237+
return (
238+
<g className="shadow-group" key={d.toISOString()}>
239+
<rect
240+
className="sf-transition shadow"
241+
opacity={active == false ? .5 : undefined}
242+
stroke={active == true ? "black" : "#ccc"}
243+
strokeWidth={active == true ? 2 : undefined}
244+
fill={r == undefined || initialLoad ? "#fff" : color(r)}
245+
width={cellSize}
246+
height={cellSize}
247+
x={day(d) * cellSize}
248+
y={week(d) * cellSize}
249+
cursor="pointer"
250+
onClick={e => r == undefined ? null : onDrillDown(r, e)}>
251+
<title>
252+
{dateFormat(d) + (r == undefined ? "" : ("(" + valueColumn.getValueNiceName(r) + ")"))}
253+
</title>
254+
</rect>
255+
</g>
256+
)
257+
})}
254258
</g>
255259

256260
<g transform={translate(rules.daysRule.start("daysContent"), rules.weeksRule.start("weeksContent"))} opacity={detector ? .5 : undefined} >

0 commit comments

Comments
 (0)