From c5418e9cd03bac9b5c5d346bd1bdb80b16340318 Mon Sep 17 00:00:00 2001 From: Holtz Yan Date: Mon, 4 Nov 2024 11:54:00 +0100 Subject: [PATCH] k --- .../hover-effect/css-descendant-selector.tsx | 80 +++++++++++++++++ .../LollipopFirstPractice copy/Graph.tsx | 43 ++++++++++ .../LollipopFirstPractice copy/data.ts | 11 +++ .../LollipopFirstPractice copy/index.js | 6 ++ .../LollipopFirstPractice copy/package.json | 29 +++++++ viz/exercise/LollipopFirstPractice/Graph.tsx | 43 ++++++++++ viz/exercise/LollipopFirstPractice/data.ts | 11 +++ viz/exercise/LollipopFirstPractice/index.js | 6 ++ .../LollipopFirstPractice/package.json | 29 +++++++ viz/exercise/LollipopFirstSolution/Graph.tsx | 83 ++++++++++++++++++ viz/exercise/LollipopFirstSolution/data.ts | 11 +++ viz/exercise/LollipopFirstSolution/index.js | 6 ++ .../LollipopFirstSolution/package.json | 29 +++++++ .../LollipopHoverEffectPractice/Graph.tsx | 84 ++++++++++++++++++ .../LollipopHoverEffectPractice/data.ts | 11 +++ .../graph.module.css | 1 + .../LollipopHoverEffectPractice/index.js | 6 ++ .../LollipopHoverEffectPractice/package.json | 29 +++++++ .../LollipopHoverEffectSolution/Graph.tsx | 85 +++++++++++++++++++ .../LollipopHoverEffectSolution/data.ts | 11 +++ .../graph.module.css | 10 +++ .../LollipopHoverEffectSolution/index.js | 6 ++ .../LollipopHoverEffectSolution/package.json | 29 +++++++ 23 files changed, 659 insertions(+) create mode 100644 viz/exercise/LollipopFirstPractice copy/Graph.tsx create mode 100644 viz/exercise/LollipopFirstPractice copy/data.ts create mode 100644 viz/exercise/LollipopFirstPractice copy/index.js create mode 100644 viz/exercise/LollipopFirstPractice copy/package.json create mode 100644 viz/exercise/LollipopFirstPractice/Graph.tsx create mode 100644 viz/exercise/LollipopFirstPractice/data.ts create mode 100644 viz/exercise/LollipopFirstPractice/index.js create mode 100644 viz/exercise/LollipopFirstPractice/package.json create mode 100644 viz/exercise/LollipopFirstSolution/Graph.tsx create mode 100644 viz/exercise/LollipopFirstSolution/data.ts create mode 100644 viz/exercise/LollipopFirstSolution/index.js create mode 100644 viz/exercise/LollipopFirstSolution/package.json create mode 100644 viz/exercise/LollipopHoverEffectPractice/Graph.tsx create mode 100644 viz/exercise/LollipopHoverEffectPractice/data.ts create mode 100644 viz/exercise/LollipopHoverEffectPractice/graph.module.css create mode 100644 viz/exercise/LollipopHoverEffectPractice/index.js create mode 100644 viz/exercise/LollipopHoverEffectPractice/package.json create mode 100644 viz/exercise/LollipopHoverEffectSolution/Graph.tsx create mode 100644 viz/exercise/LollipopHoverEffectSolution/data.ts create mode 100644 viz/exercise/LollipopHoverEffectSolution/graph.module.css create mode 100644 viz/exercise/LollipopHoverEffectSolution/index.js create mode 100644 viz/exercise/LollipopHoverEffectSolution/package.json diff --git a/pages/course/hover-effect/css-descendant-selector.tsx b/pages/course/hover-effect/css-descendant-selector.tsx index ccfabe75..b472a6f4 100644 --- a/pages/course/hover-effect/css-descendant-selector.tsx +++ b/pages/course/hover-effect/css-descendant-selector.tsx @@ -8,6 +8,13 @@ import { Badge } from '@/component/UI/badge'; import GraphGallery from '@/component/GraphGallery'; import { TreemapHoverEffectDemo } from '@/viz/TreemapHoverEffect/TreemapHoverEffectDemo'; import Link from 'next/link'; +import { + Exercise, + ExerciseDoubleSandbox, +} from '@/component/ExerciseDoubleSandbox'; +import { ExerciseAccordion } from '@/component/ExerciseAccordion'; +import { Graph9 } from '@/viz/exercise/LollipopFirstSolution/Graph'; +import { Graph12 } from '@/viz/exercise/LollipopHoverEffectSolution/Graph'; const previousURL = '/course/hover-effect/css-pseudo-class'; const currentURL = '/course/hover-effect/css-descendant-selector'; @@ -135,6 +142,79 @@ export default function Home() { 'streamgraph-hover-effect.gif', ]} /> + + {/* - +- +- +- +- +- +- */} +

Exercices

+ Your first lollipop! 🍭, + content: , + }, + { + title: Lollipop with hover effect, + content: , + }, + ]} + /> ); } + +const exercises: Exercise[] = [ + { + whyItMatters: ( + <> +

+ With the SVG and d3 fundations you have, it is a breeze to create a + new chart type! +

+ + ), + toDo: ( + <> +
    +
  • A dataset is provided in the sandbox folder
  • +
  • + Build a cleveland chart with this dataset. (It is a variation of the + lollipop, handy to compare 2 values across many groups!) +
  • +
  • Check the solution tab to see how the graph must look like
  • +
+ + ), + practiceSandbox: 'exercise/LollipopFirstPractice', + solutionSandbox: 'exercise/LollipopFirstSolution', + }, + + { + whyItMatters: ( + <> +

+ Descendant selectors are so good to create fancy hover effect using + CSS only! +

+ + ), + toDo: ( + <> +
    +
  • Add a class for the SVG container
  • +
  • + Using CSS descendant selector, highlight the hovered row, and dim + other rows. +
  • +
+ + ), + practiceSandbox: 'exercise/LollipopHoverEffectPractice', + solutionSandbox: 'exercise/LollipopHoverEffectSolution', + }, +]; diff --git a/viz/exercise/LollipopFirstPractice copy/Graph.tsx b/viz/exercise/LollipopFirstPractice copy/Graph.tsx new file mode 100644 index 00000000..fc5eca04 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice copy/Graph.tsx @@ -0,0 +1,43 @@ +import * as d3 from 'd3'; +import { data } from './data'; + +const width = 500; +const height = 500; +const MARGIN = { top: 30, right: 30, bottom: 30, left: 80 }; + +export const Graph = () => { + const boundsWidth = width - MARGIN.right - MARGIN.left; + const boundsHeight = height - MARGIN.top - MARGIN.bottom; + + // Y axis is for groups + const groups = data.sort((a, b) => b.value1 - a.value1).map((d) => d.name); + const yScale = d3.scaleBand().domain(groups).range([0, boundsHeight]); + + // X axis + const [min, max] = d3.extent(data.map((d) => d.value1)); + + const xScale = d3 + .scaleLinear() + .domain([0, max || 10]) + .range([0, boundsWidth]); + + // Build the shapes + const allShapes = data.map((d, i) => { + // TODO + return null; + }); + + return ( +
+ + + {allShapes} + + +
+ ); +}; diff --git a/viz/exercise/LollipopFirstPractice copy/data.ts b/viz/exercise/LollipopFirstPractice copy/data.ts new file mode 100644 index 00000000..d26cd957 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice copy/data.ts @@ -0,0 +1,11 @@ +export const data = [ + { name: "Mark", value1: 90, value2: 72 }, + { name: "Robert", value1: 12, value2: 10 }, + { name: "Emily", value1: 34, value2: 14 }, + { name: "Marion", value1: 53, value2: 24 }, + { name: "Nicolas", value1: 98, value2: 58 }, + { name: "Mélanie", value1: 23, value2: 20 }, + { name: "Gabriel", value1: 18, value2: 10 }, + { name: "Jean", value1: 104, value2: 70 }, + { name: "Paul", value1: 2, value2: 1 }, +] diff --git a/viz/exercise/LollipopFirstPractice copy/index.js b/viz/exercise/LollipopFirstPractice copy/index.js new file mode 100644 index 00000000..fa564d27 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice copy/index.js @@ -0,0 +1,6 @@ +// File used to render something in codesandbox only +import ReactDOM from 'react-dom'; +import { Graph } from './Graph'; + +const rootElement = document.getElementById('root'); +ReactDOM.render(, rootElement); diff --git a/viz/exercise/LollipopFirstPractice copy/package.json b/viz/exercise/LollipopFirstPractice copy/package.json new file mode 100644 index 00000000..84ed1985 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice copy/package.json @@ -0,0 +1,29 @@ +{ + "name": "pie-chart-basic", + "version": "1.0.0", + "description": "", + "keywords": [], + "main": "index.js", + "dependencies": { + "react": "17.0.2", + "d3": "7.1.1", + "react-dom": "17.0.2", + "react-scripts": "4.0.0" + }, + "devDependencies": { + "@babel/runtime": "7.13.8", + "typescript": "4.1.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/viz/exercise/LollipopFirstPractice/Graph.tsx b/viz/exercise/LollipopFirstPractice/Graph.tsx new file mode 100644 index 00000000..fc5eca04 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice/Graph.tsx @@ -0,0 +1,43 @@ +import * as d3 from 'd3'; +import { data } from './data'; + +const width = 500; +const height = 500; +const MARGIN = { top: 30, right: 30, bottom: 30, left: 80 }; + +export const Graph = () => { + const boundsWidth = width - MARGIN.right - MARGIN.left; + const boundsHeight = height - MARGIN.top - MARGIN.bottom; + + // Y axis is for groups + const groups = data.sort((a, b) => b.value1 - a.value1).map((d) => d.name); + const yScale = d3.scaleBand().domain(groups).range([0, boundsHeight]); + + // X axis + const [min, max] = d3.extent(data.map((d) => d.value1)); + + const xScale = d3 + .scaleLinear() + .domain([0, max || 10]) + .range([0, boundsWidth]); + + // Build the shapes + const allShapes = data.map((d, i) => { + // TODO + return null; + }); + + return ( +
+ + + {allShapes} + + +
+ ); +}; diff --git a/viz/exercise/LollipopFirstPractice/data.ts b/viz/exercise/LollipopFirstPractice/data.ts new file mode 100644 index 00000000..d26cd957 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice/data.ts @@ -0,0 +1,11 @@ +export const data = [ + { name: "Mark", value1: 90, value2: 72 }, + { name: "Robert", value1: 12, value2: 10 }, + { name: "Emily", value1: 34, value2: 14 }, + { name: "Marion", value1: 53, value2: 24 }, + { name: "Nicolas", value1: 98, value2: 58 }, + { name: "Mélanie", value1: 23, value2: 20 }, + { name: "Gabriel", value1: 18, value2: 10 }, + { name: "Jean", value1: 104, value2: 70 }, + { name: "Paul", value1: 2, value2: 1 }, +] diff --git a/viz/exercise/LollipopFirstPractice/index.js b/viz/exercise/LollipopFirstPractice/index.js new file mode 100644 index 00000000..fa564d27 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice/index.js @@ -0,0 +1,6 @@ +// File used to render something in codesandbox only +import ReactDOM from 'react-dom'; +import { Graph } from './Graph'; + +const rootElement = document.getElementById('root'); +ReactDOM.render(, rootElement); diff --git a/viz/exercise/LollipopFirstPractice/package.json b/viz/exercise/LollipopFirstPractice/package.json new file mode 100644 index 00000000..84ed1985 --- /dev/null +++ b/viz/exercise/LollipopFirstPractice/package.json @@ -0,0 +1,29 @@ +{ + "name": "pie-chart-basic", + "version": "1.0.0", + "description": "", + "keywords": [], + "main": "index.js", + "dependencies": { + "react": "17.0.2", + "d3": "7.1.1", + "react-dom": "17.0.2", + "react-scripts": "4.0.0" + }, + "devDependencies": { + "@babel/runtime": "7.13.8", + "typescript": "4.1.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/viz/exercise/LollipopFirstSolution/Graph.tsx b/viz/exercise/LollipopFirstSolution/Graph.tsx new file mode 100644 index 00000000..608143b9 --- /dev/null +++ b/viz/exercise/LollipopFirstSolution/Graph.tsx @@ -0,0 +1,83 @@ +import * as d3 from 'd3'; +import { data } from './data'; + +const width = 500; +const height = 500; +const MARGIN = { top: 30, right: 30, bottom: 30, left: 80 }; + +export const Graph = () => { + const boundsWidth = width - MARGIN.right - MARGIN.left; + const boundsHeight = height - MARGIN.top - MARGIN.bottom; + + // Y axis is for groups + const groups = data.sort((a, b) => b.value1 - a.value1).map((d) => d.name); + const yScale = d3.scaleBand().domain(groups).range([0, boundsHeight]); + + // X axis + const [min, max] = d3.extent(data.map((d) => d.value1)); + + const xScale = d3 + .scaleLinear() + .domain([0, max || 10]) + .range([0, boundsWidth]); + + // Build the shapes + const allShapes = data.map((d, i) => { + const y = yScale(d.name) + yScale.bandwidth() / 2; + + return ( + + + + + + {d.name} + + + ); + }); + + return ( +
+ + + {allShapes} + + +
+ ); +}; diff --git a/viz/exercise/LollipopFirstSolution/data.ts b/viz/exercise/LollipopFirstSolution/data.ts new file mode 100644 index 00000000..d26cd957 --- /dev/null +++ b/viz/exercise/LollipopFirstSolution/data.ts @@ -0,0 +1,11 @@ +export const data = [ + { name: "Mark", value1: 90, value2: 72 }, + { name: "Robert", value1: 12, value2: 10 }, + { name: "Emily", value1: 34, value2: 14 }, + { name: "Marion", value1: 53, value2: 24 }, + { name: "Nicolas", value1: 98, value2: 58 }, + { name: "Mélanie", value1: 23, value2: 20 }, + { name: "Gabriel", value1: 18, value2: 10 }, + { name: "Jean", value1: 104, value2: 70 }, + { name: "Paul", value1: 2, value2: 1 }, +] diff --git a/viz/exercise/LollipopFirstSolution/index.js b/viz/exercise/LollipopFirstSolution/index.js new file mode 100644 index 00000000..fa564d27 --- /dev/null +++ b/viz/exercise/LollipopFirstSolution/index.js @@ -0,0 +1,6 @@ +// File used to render something in codesandbox only +import ReactDOM from 'react-dom'; +import { Graph } from './Graph'; + +const rootElement = document.getElementById('root'); +ReactDOM.render(, rootElement); diff --git a/viz/exercise/LollipopFirstSolution/package.json b/viz/exercise/LollipopFirstSolution/package.json new file mode 100644 index 00000000..84ed1985 --- /dev/null +++ b/viz/exercise/LollipopFirstSolution/package.json @@ -0,0 +1,29 @@ +{ + "name": "pie-chart-basic", + "version": "1.0.0", + "description": "", + "keywords": [], + "main": "index.js", + "dependencies": { + "react": "17.0.2", + "d3": "7.1.1", + "react-dom": "17.0.2", + "react-scripts": "4.0.0" + }, + "devDependencies": { + "@babel/runtime": "7.13.8", + "typescript": "4.1.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/viz/exercise/LollipopHoverEffectPractice/Graph.tsx b/viz/exercise/LollipopHoverEffectPractice/Graph.tsx new file mode 100644 index 00000000..5ca091ab --- /dev/null +++ b/viz/exercise/LollipopHoverEffectPractice/Graph.tsx @@ -0,0 +1,84 @@ +import * as d3 from 'd3'; +import { data } from './data'; +import styles from './graph.module.css'; + +const width = 500; +const height = 500; +const MARGIN = { top: 30, right: 30, bottom: 30, left: 80 }; + +export const Graph = () => { + const boundsWidth = width - MARGIN.right - MARGIN.left; + const boundsHeight = height - MARGIN.top - MARGIN.bottom; + + // Y axis is for groups + const groups = data.sort((a, b) => b.value1 - a.value1).map((d) => d.name); + const yScale = d3.scaleBand().domain(groups).range([0, boundsHeight]); + + // X axis + const [min, max] = d3.extent(data.map((d) => d.value1)); + + const xScale = d3 + .scaleLinear() + .domain([0, max || 10]) + .range([0, boundsWidth]); + + // Build the shapes + const allShapes = data.map((d, i) => { + const y = yScale(d.name) + yScale.bandwidth() / 2; + + return ( + + + + + + {d.name} + + + ); + }); + + return ( +
+ + + {allShapes} + + +
+ ); +}; diff --git a/viz/exercise/LollipopHoverEffectPractice/data.ts b/viz/exercise/LollipopHoverEffectPractice/data.ts new file mode 100644 index 00000000..d26cd957 --- /dev/null +++ b/viz/exercise/LollipopHoverEffectPractice/data.ts @@ -0,0 +1,11 @@ +export const data = [ + { name: "Mark", value1: 90, value2: 72 }, + { name: "Robert", value1: 12, value2: 10 }, + { name: "Emily", value1: 34, value2: 14 }, + { name: "Marion", value1: 53, value2: 24 }, + { name: "Nicolas", value1: 98, value2: 58 }, + { name: "Mélanie", value1: 23, value2: 20 }, + { name: "Gabriel", value1: 18, value2: 10 }, + { name: "Jean", value1: 104, value2: 70 }, + { name: "Paul", value1: 2, value2: 1 }, +] diff --git a/viz/exercise/LollipopHoverEffectPractice/graph.module.css b/viz/exercise/LollipopHoverEffectPractice/graph.module.css new file mode 100644 index 00000000..8f414f58 --- /dev/null +++ b/viz/exercise/LollipopHoverEffectPractice/graph.module.css @@ -0,0 +1 @@ +/* TODO */ diff --git a/viz/exercise/LollipopHoverEffectPractice/index.js b/viz/exercise/LollipopHoverEffectPractice/index.js new file mode 100644 index 00000000..fa564d27 --- /dev/null +++ b/viz/exercise/LollipopHoverEffectPractice/index.js @@ -0,0 +1,6 @@ +// File used to render something in codesandbox only +import ReactDOM from 'react-dom'; +import { Graph } from './Graph'; + +const rootElement = document.getElementById('root'); +ReactDOM.render(, rootElement); diff --git a/viz/exercise/LollipopHoverEffectPractice/package.json b/viz/exercise/LollipopHoverEffectPractice/package.json new file mode 100644 index 00000000..84ed1985 --- /dev/null +++ b/viz/exercise/LollipopHoverEffectPractice/package.json @@ -0,0 +1,29 @@ +{ + "name": "pie-chart-basic", + "version": "1.0.0", + "description": "", + "keywords": [], + "main": "index.js", + "dependencies": { + "react": "17.0.2", + "d3": "7.1.1", + "react-dom": "17.0.2", + "react-scripts": "4.0.0" + }, + "devDependencies": { + "@babel/runtime": "7.13.8", + "typescript": "4.1.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/viz/exercise/LollipopHoverEffectSolution/Graph.tsx b/viz/exercise/LollipopHoverEffectSolution/Graph.tsx new file mode 100644 index 00000000..b6d21e1f --- /dev/null +++ b/viz/exercise/LollipopHoverEffectSolution/Graph.tsx @@ -0,0 +1,85 @@ +import * as d3 from 'd3'; +import { data } from './data'; +import styles from './graph.module.css'; + +const width = 500; +const height = 500; +const MARGIN = { top: 30, right: 30, bottom: 30, left: 80 }; + +export const Graph = () => { + const boundsWidth = width - MARGIN.right - MARGIN.left; + const boundsHeight = height - MARGIN.top - MARGIN.bottom; + + // Y axis is for groups + const groups = data.sort((a, b) => b.value1 - a.value1).map((d) => d.name); + const yScale = d3.scaleBand().domain(groups).range([0, boundsHeight]); + + // X axis + const [min, max] = d3.extent(data.map((d) => d.value1)); + + const xScale = d3 + .scaleLinear() + .domain([0, max || 10]) + .range([0, boundsWidth]); + + // Build the shapes + const allShapes = data.map((d, i) => { + const y = yScale(d.name) + yScale.bandwidth() / 2; + + return ( + + + + + + {d.name} + + + ); + }); + + return ( +
+ + + {allShapes} + + +
+ ); +}; diff --git a/viz/exercise/LollipopHoverEffectSolution/data.ts b/viz/exercise/LollipopHoverEffectSolution/data.ts new file mode 100644 index 00000000..d26cd957 --- /dev/null +++ b/viz/exercise/LollipopHoverEffectSolution/data.ts @@ -0,0 +1,11 @@ +export const data = [ + { name: "Mark", value1: 90, value2: 72 }, + { name: "Robert", value1: 12, value2: 10 }, + { name: "Emily", value1: 34, value2: 14 }, + { name: "Marion", value1: 53, value2: 24 }, + { name: "Nicolas", value1: 98, value2: 58 }, + { name: "Mélanie", value1: 23, value2: 20 }, + { name: "Gabriel", value1: 18, value2: 10 }, + { name: "Jean", value1: 104, value2: 70 }, + { name: "Paul", value1: 2, value2: 1 }, +] diff --git a/viz/exercise/LollipopHoverEffectSolution/graph.module.css b/viz/exercise/LollipopHoverEffectSolution/graph.module.css new file mode 100644 index 00000000..0f7a973a --- /dev/null +++ b/viz/exercise/LollipopHoverEffectSolution/graph.module.css @@ -0,0 +1,10 @@ +.row { + opacity: 0.8; + cursor: pointer; +} +.rowsContainer:hover .row { + opacity: 0.3; +} +.rowsContainer .row:hover { + opacity: 1; +} diff --git a/viz/exercise/LollipopHoverEffectSolution/index.js b/viz/exercise/LollipopHoverEffectSolution/index.js new file mode 100644 index 00000000..fa564d27 --- /dev/null +++ b/viz/exercise/LollipopHoverEffectSolution/index.js @@ -0,0 +1,6 @@ +// File used to render something in codesandbox only +import ReactDOM from 'react-dom'; +import { Graph } from './Graph'; + +const rootElement = document.getElementById('root'); +ReactDOM.render(, rootElement); diff --git a/viz/exercise/LollipopHoverEffectSolution/package.json b/viz/exercise/LollipopHoverEffectSolution/package.json new file mode 100644 index 00000000..84ed1985 --- /dev/null +++ b/viz/exercise/LollipopHoverEffectSolution/package.json @@ -0,0 +1,29 @@ +{ + "name": "pie-chart-basic", + "version": "1.0.0", + "description": "", + "keywords": [], + "main": "index.js", + "dependencies": { + "react": "17.0.2", + "d3": "7.1.1", + "react-dom": "17.0.2", + "react-scripts": "4.0.0" + }, + "devDependencies": { + "@babel/runtime": "7.13.8", + "typescript": "4.1.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +}