Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
D
DV-Project
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
almohanad.hafez
DV-Project
Commits
1af0a999
Commit
1af0a999
authored
Feb 13, 2025
by
Almouhannad Hafez
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add pie chart
parent
d7250a02
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
331 additions
and
1 deletion
+331
-1
pie-chart-helper.ts
src/ts/charts-helpers/pie-chart-helper.ts
+91
-0
pie-chart.ts
src/ts/charts/pie-chart.ts
+234
-0
main.ts
src/ts/main.ts
+6
-1
No files found.
src/ts/charts-helpers/pie-chart-helper.ts
0 → 100644
View file @
1af0a999
import
*
as
d3
from
'd3'
;
import
{
ChartConfiguration
}
from
"../chart-base/chart-configuration"
;
import
{
PieChart
}
from
"../charts/pie-chart"
;
export
class
PieChartHelper
{
private
container
:
any
;
private
containerId
:
string
=
'pie-chart-container'
;
private
svgId
:
string
=
'pie-chart'
;
private
selector
:
any
;
private
config
:
ChartConfiguration
=
new
ChartConfiguration
(
`#
${
this
.
svgId
}
`
);
private
chart
:
PieChart
;
private
data
:
any
[]
=
[];
public
setData
(
data
:
any
)
{
this
.
data
=
data
;
}
public
appendChart
()
{
// Add div container
this
.
container
=
d3
.
select
(
'#medalDistributionByGenderChart'
)
.
append
(
'div'
)
.
attr
(
'class'
,
'container'
)
.
attr
(
'style'
,
'width: fit-content;'
)
.
attr
(
'id'
,
`
${
this
.
containerId
}
`
);
// Add year selector
this
.
selector
=
this
.
container
.
append
(
'div'
)
.
attr
(
'id'
,
'pie-chart-selector'
)
.
attr
(
'class'
,
'text-center'
);
this
.
selector
.
append
(
'label'
)
.
attr
(
'for'
,
'yearSelector'
)
.
attr
(
'class'
,
'form-label'
)
.
text
(
'Select year:'
);
this
.
selector
.
append
(
'select'
)
.
attr
(
'class'
,
'form-select text-center'
)
.
append
(
'option'
).
attr
(
'value'
,
'all'
).
text
(
'All years'
);
const
selectField
=
this
.
selector
.
select
(
'select'
);
const
years
=
Array
.
from
(
new
Set
(
this
.
data
.
map
(
d
=>
d
.
Year
)));
// Get unique years
years
.
forEach
(
year
=>
{
selectField
.
append
(
"option"
)
.
attr
(
"value"
,
year
)
.
text
(
year
);
});
// add select event handler
const
visHelper
=
this
;
selectField
.
on
(
"change"
,
function
(
event
:
any
)
{
const
selectedYear
=
d3
.
select
(
event
.
target
).
property
(
"value"
);
visHelper
.
updateChart
(
selectedYear
);
});
// add svg for chart
this
.
container
.
append
(
'svg'
)
.
attr
(
'id'
,
`
${
this
.
svgId
}
`
);
// init chart
this
.
chart
=
new
PieChart
(
this
.
config
);
this
.
updateChart
(
"all"
);
}
private
updateChart
(
selectedYear
:
string
)
{
this
.
chart
.
data
=
this
.
processData
(
selectedYear
);
this
.
chart
.
updateVis
();
}
private
processData
(
selectedYear
:
string
)
{
const
filteredData
=
selectedYear
!==
'all'
?
this
.
data
.
filter
(
d
=>
d
.
Year
===
selectedYear
)
:
this
.
data
;
const
genderCountMap
:
{
[
key
:
string
]:
number
}
=
{};
filteredData
.
forEach
(
d
=>
{
const
gender
=
d
.
Gender
;
if
(
genderCountMap
[
gender
])
{
genderCountMap
[
gender
]
++
;
}
else
{
genderCountMap
[
gender
]
=
1
;
}
});
const
result
:
any
[]
=
Object
.
keys
(
genderCountMap
).
map
(
key
=>
({
key
:
key
,
value
:
genderCountMap
[
key
]
}));
return
result
;
}
}
src/ts/charts/pie-chart.ts
0 → 100644
View file @
1af0a999
import
*
as
d3
from
'd3'
;
import
{
Chart
}
from
'../chart-base/chart'
;
import
{
ChartConfiguration
}
from
'../chart-base/chart-configuration'
;
interface
pieChartDatatype
{
key
:
string
;
value
:
number
;
}
export
class
PieChart
extends
Chart
{
private
pie
:
any
;
private
arc
:
any
;
private
color
:
any
;
tooltip
:
d3
.
Selection
<
HTMLDivElement
,
unknown
,
HTMLElement
,
any
>
;
constructor
(
_config
:
ChartConfiguration
,
_data
?:
pieChartDatatype
[])
{
super
(
_config
,
_data
);
this
.
initVis
();
}
protected
getDefaultMargins
()
{
return
{
top
:
20
,
right
:
80
,
bottom
:
20
,
left
:
20
};
}
protected
getDefaultContainerSize
()
{
return
{
width
:
400
,
height
:
400
};
}
protected
initVis
()
{
const
vis
=
this
;
// Define the pie layout
vis
.
pie
=
d3
.
pie
<
any
>
()
.
value
(
d
=>
d
.
value
)
.
sort
(
null
);
// Define the arc
vis
.
arc
=
d3
.
arc
()
.
innerRadius
(
0
)
.
outerRadius
(
Math
.
min
(
vis
.
width
,
vis
.
height
)
/
2
-
1
);
// Define color scale for gender
// Can use scaleOrdinal10 for generalization and usage of keys
// instead of hard coded keys
vis
.
color
=
d3
.
scaleOrdinal
()
.
domain
([
'Men'
,
'Women'
])
.
range
([
'#01a7c5'
,
'#f284a5'
]);
// Add tooltip
this
.
tooltip
=
d3
.
select
(
'body'
).
append
(
'div'
)
.
attr
(
'id'
,
'pie-chart-tooltip'
)
.
style
(
'opacity'
,
0
)
.
style
(
'position'
,
'absolute'
)
.
style
(
'background'
,
'white'
)
.
style
(
'border'
,
'1px solid black'
)
.
style
(
'padding'
,
'5px'
)
.
style
(
'pointer-events'
,
'none'
);
}
public
updateVis
()
{
const
vis
=
this
;
vis
.
renderVis
();
}
protected
renderVis
()
{
const
vis
=
this
;
// Clear previous
vis
.
chart
.
selectAll
(
"*"
).
remove
();
// chart gropu
const
g
=
vis
.
chart
.
append
(
"g"
)
.
attr
(
"transform"
,
`translate(
${
vis
.
width
/
2
}
,
${
vis
.
height
/
2
}
)`
);
// Create args
const
pieData
=
vis
.
pie
(
this
.
data
);
// Total omong all values
const
total
=
d3
.
sum
(
vis
.
data
,
(
data
:
any
)
=>
data
.
value
);
// Bind data to arcs
const
arcs
=
g
.
selectAll
(
".arc"
)
.
data
(
pieData
,
(
d
:
any
)
=>
d
.
data
.
key
);
// Enter section (new arcs)
const
arcsEnter
=
arcs
.
enter
().
append
(
"g"
)
.
attr
(
"class"
,
"arc"
);
arcsEnter
.
append
(
"path"
)
.
attr
(
"d"
,
vis
.
arc
)
.
attr
(
"fill"
,
(
d
:
any
)
=>
vis
.
color
(
d
.
data
.
key
))
.
attr
(
"stroke"
,
"#fff"
)
.
attr
(
"stroke-width"
,
0
)
.
attr
(
"opacity"
,
0
)
// To be changed in transition
.
transition
()
.
duration
(
500
)
.
attr
(
"opacity"
,
1
);
// Add percentage text to each arc
arcsEnter
.
append
(
"text"
)
.
attr
(
"transform"
,
(
d
:
any
)
=>
`translate(
${
vis
.
arc
.
centroid
(
d
)}
)`
)
// Position text at the centroid of the arc
.
attr
(
"dy"
,
".35em"
)
// Adjust vertical alignment
.
attr
(
"text-anchor"
,
"middle"
)
// Center the text
.
text
((
d
:
any
)
=>
{
const
percentage
=
((
d
.
data
.
value
/
total
)
*
100
).
toFixed
(
2
);
return
`
${
percentage
}
%`
;
// Display percentage
})
.
style
(
"fill"
,
"#fff"
)
.
style
(
"user-select"
,
'none'
)
.
style
(
'font-weight'
,
'bold'
);
// Add mouse event handlers for arcs
arcsEnter
.
select
(
"path"
)
.
on
(
"mouseover"
,
function
(
event
:
any
,
d
:
any
)
{
// Highlight the hovered arc
d3
.
select
(
event
.
currentTarget
).
attr
(
"opacity"
,
1
);
// Set hovered arc to full opacity
// minimum opacity of other arcs
arcsEnter
.
selectAll
(
"path"
).
filter
(
function
(
arcData
:
any
)
{
return
arcData
.
data
.
key
!==
d
.
data
.
key
;
}).
attr
(
"opacity"
,
0.4
);
// high stroke to distinguish between args when select
arcsEnter
.
selectAll
(
"path"
).
filter
(
function
(
arcData
:
any
)
{
return
arcData
.
data
.
key
===
d
.
data
.
key
;
}).
attr
(
"stroke-width"
,
6
);
vis
.
tooltip
.
style
(
'display'
,
'block'
)
.
style
(
'opacity'
,
.
9
);
vis
.
tooltip
.
html
(
`
${
d
.
data
.
key
}
<br>Total Medals:
${
d
.
data
.
value
}
`
)
.
style
(
'left'
,
(
event
.
pageX
-
20
)
+
'px'
)
.
style
(
'top'
,
(
event
.
pageY
-
85
)
+
'px'
);
})
.
on
(
"mousemove"
,
function
(
event
:
any
)
{
vis
.
tooltip
.
style
(
'left'
,
(
event
.
pageX
-
20
)
+
'px'
)
.
style
(
'top'
,
(
event
.
pageY
-
85
)
+
'px'
);
})
.
on
(
"mouseout"
,
function
()
{
// Reset opacity of all arcs
arcsEnter
.
selectAll
(
"path"
).
attr
(
"opacity"
,
1
).
attr
(
'stroke-width'
,
0
);
// Reset all arcs to full opacity
vis
.
tooltip
.
style
(
'display'
,
'none'
);
});
// Update section
arcs
.
select
(
"path"
)
.
transition
()
// smooth updates
.
duration
(
500
)
.
attr
(
"d"
,
vis
.
arc
)
.
attr
(
"fill"
,
(
d
:
any
)
=>
vis
.
color
(
d
.
data
.
key
))
.
attr
(
"stroke"
,
"#fff"
)
.
attr
(
"stroke-width"
,
2
)
// Update text for existing arcs
arcs
.
select
(
"text"
)
.
transition
()
// Transition for text change
.
duration
(
500
)
.
attr
(
"transform"
,
(
d
:
any
)
=>
`translate(
${
vis
.
arc
.
centroid
(
d
)}
)`
)
// Update position
.
text
((
d
:
any
)
=>
{
const
percentage
=
((
d
.
data
.
value
/
total
)
*
100
).
toFixed
(
2
);
return
`
${
percentage
}
%`
;
// Update percentage text
});
// Exit
arcs
.
exit
().
select
(
"path"
)
.
transition
()
.
duration
(
500
)
.
attr
(
"opacity"
,
0
)
// Fade out effects
.
remove
();
// Legend
// this is in render for cases when keys changes when data changes (general case)
const
legend
=
vis
.
chart
.
append
(
"g"
)
.
attr
(
"transform"
,
`translate(
${
vis
.
width
}
,
${
vis
.
height
-
60
}
)`
);
// Find all keys
const
legendData
=
this
.
data
.
map
(
d
=>
d
.
key
);
// Bind data to legend items
const
legendItems
=
legend
.
selectAll
(
".legend-item"
)
.
data
(
legendData
);
// Enter: create new legend items
const
legendEnter
=
legendItems
.
enter
().
append
(
"g"
)
.
attr
(
"class"
,
"legend-item"
)
.
attr
(
"transform"
,
(
_
:
any
,
i
:
any
)
=>
`translate(0,
${
i
*
20
}
)`
);
legendEnter
.
append
(
"rect"
)
.
attr
(
"x"
,
0
)
.
attr
(
"width"
,
18
)
.
attr
(
"height"
,
18
)
.
attr
(
"fill"
,
(
d
:
any
)
=>
vis
.
color
(
d
))
.
attr
(
"stroke"
,
"#fff"
);
legendEnter
.
append
(
"text"
)
.
attr
(
"x"
,
25
)
.
attr
(
"y"
,
9
)
.
attr
(
"dy"
,
".35em"
)
.
text
((
d
:
any
)
=>
d
)
.
style
(
"user-select"
,
"none"
);
// Update
const
legendUpdate
=
legendItems
.
merge
(
legendEnter
);
legendUpdate
.
select
(
"rect"
)
.
transition
()
// Transition for color change
.
duration
(
500
)
.
attr
(
"fill"
,
(
d
:
any
)
=>
vis
.
color
(
d
));
legendUpdate
.
select
(
"text"
)
.
transition
()
// Transition for text change
.
duration
(
500
)
.
text
((
d
:
any
)
=>
d
);
// Events handlers
legendUpdate
.
on
(
"mouseover"
,
function
(
_
:
any
,
d
:
any
)
{
// Highlight related arc
arcsEnter
.
selectAll
(
"path"
).
filter
(
function
(
arcData
:
any
)
{
return
arcData
.
data
.
key
===
d
;
}).
attr
(
"opacity"
,
1
).
attr
(
"stroke-width"
,
6
);
// Reduce opacity of other arcs
arcsEnter
.
selectAll
(
"path"
).
filter
(
function
(
arcData
:
any
)
{
return
arcData
.
data
.
key
!==
d
;
}).
attr
(
"opacity"
,
0.4
);
})
.
on
(
"mouseout"
,
function
()
{
// Reset opacity of all arcs
arcsEnter
.
selectAll
(
"path"
).
attr
(
"opacity"
,
1
).
attr
(
'stroke-width'
,
0
);
// Reset all arcs to full opacity
});
// Exit
legendItems
.
exit
().
transition
()
.
duration
(
500
)
.
attr
(
"opacity"
,
0
)
// Fade out
.
remove
();
// Remove after fade out
}
}
\ No newline at end of file
src/ts/main.ts
View file @
1af0a999
...
...
@@ -3,10 +3,12 @@ import '/src/css/style.css';
import
'bootstrap'
;
import
{
StackedBarChartHelper
}
from
'./charts-helpers/stacked-bar-chart-helper'
;
import
{
MultiLineChartHelper
}
from
'./charts-helpers/multi-line-chart-helper'
;
import
{
PieChartHelper
}
from
'./charts-helpers/pie-chart-helper'
;
const
datasetPath
=
import
.
meta
.
env
.
VITE_DATASET_PATH
;
let
rawData
:
any
[];
const
stackerBarChartHelper
=
new
StackedBarChartHelper
();
const
multiLineChartHelper
=
new
MultiLineChartHelper
();
const
pieChartHelper
=
new
PieChartHelper
();
d3
.
csv
(
datasetPath
).
then
(
data
=>
{
rawData
=
data
.
filter
(
d
=>
d
.
Medal
!==
''
);
...
...
@@ -17,5 +19,8 @@ d3.csv(datasetPath).then(data => {
multiLineChartHelper
.
setData
(
rawData
);
multiLineChartHelper
.
appendChart
();
});
pieChartHelper
.
setData
(
rawData
);
pieChartHelper
.
appendChart
();
});
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment