From 8dd3c21aa6889d4180a1ba644a913765859c16ad Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Wed, 9 Oct 2024 17:25:47 -0400 Subject: [PATCH 1/5] chart-lib.js: Expand barChart() to produce the related dot chart if the dot-chart option set #469 chart.arr: Added dot-chart-from-list() dot-chart-test.arr added --- src/web/arr/trove/chart.arr | 38 ++++++++++++++++ src/web/js/trove/chart-lib.js | 43 ++++++++++++++++++- .../pyret-programs/charts/dot-chart-test.arr | 18 ++++++++ .../charts/image-bar-chart-test.arr | 5 ++- 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 test-util/pyret-programs/charts/dot-chart-test.arr diff --git a/src/web/arr/trove/chart.arr b/src/web/arr/trove/chart.arr index e828dc7e..b226db4f 100644 --- a/src/web/arr/trove/chart.arr +++ b/src/web/arr/trove/chart.arr @@ -1067,6 +1067,7 @@ default-bar-chart-series = { pointer-color: none, axisdata: none, horizontal: false, + dot-chart: false, default-interval-color: none } @@ -1917,6 +1918,42 @@ fun bar-chart-from-list(labels :: P.LoS, values :: P.LoN) -> DataSeries block: data-series.make-axis(max-positive-height, max-negative-height) end +fun dot-chart-from-list(labels :: P.LoS, values :: P.LoN) -> DataSeries block: + doc: ``` + Consume labels, a list of string, and values, a list of numbers + and construct a dot chart + ``` + # Type Checking + values.each(check-num) + labels.each(check-string) + + # Constants + label-length = labels.length() + value-length = values.length() + rational-values = map(num-to-rational, values) + + # Edge Case Error Checking + when value-length == 0: + raise("dot-chart: can't have empty data") + end + when label-length <> value-length: + raise('dot-chart: labels and values should have the same length') + end + + {max-positive-height; max-negative-height} = prep-axis(rational-values) + + data-series = default-bar-chart-series.{ + tab: to-table2-n(labels, rational-values), + dot-chart: true, + axis-top: max-positive-height, + axis-bottom: max-negative-height, + annotations: values.map({(_): [list: none]}) ^ list-to-table2, + intervals: values.map({(_): [list: [raw-array: ]]}) ^ list-to-table2, + } ^ bar-chart-series + + data-series.make-axis(max-positive-height, max-negative-height) +end + fun grouped-bar-chart-from-list( labels :: P.LoS, value-lists :: P.LoLoN, @@ -2613,6 +2650,7 @@ from-list = { exploding-pie-chart: exploding-pie-chart-from-list, image-pie-chart: image-pie-chart-from-list, bar-chart: bar-chart-from-list, + dot-chart: dot-chart-from-list, image-bar-chart: image-bar-chart-from-list, grouped-bar-chart: grouped-bar-chart-from-list, stacked-bar-chart: stacked-bar-chart-from-list, diff --git a/src/web/js/trove/chart-lib.js b/src/web/js/trove/chart-lib.js index 604bd8fc..36f7f5ea 100644 --- a/src/web/js/trove/chart-lib.js +++ b/src/web/js/trove/chart-lib.js @@ -700,6 +700,7 @@ // ASSERT: if we're using custom images, there will be a 4th column const hasImage = table[0].length == 4; + const dotChartP = get(rawData, 'dot-chart'); // Adds each row of bar data and bar_color data table.forEach(function (row) { @@ -717,7 +718,7 @@ color : interval_color, }, series : { - 0 : { dataOpacity : hasImage? 0 : 1.0 } + 0 : { dataOpacity : (hasImage || dotChartP)? 0 : 1.0 } } }; @@ -761,10 +762,13 @@ onExit: defaultImageReturn, mutators: [backgroundMutator, axesNameMutator, yAxisRangeMutator], overlay: (overlay, restarter, chart, container) => { - if(!hasImage) return; + + if (!hasImage && !dotChartP) return; // if custom images are defined, use the image at that location // and overlay it atop each dot + + google.visualization.events.addListener(chart, 'ready', function () { // HACK(Emmanuel): // If Google changes the DOM for charts, these lines will likely break @@ -772,6 +776,7 @@ const rects = svgRoot.children[1].children[1].children[1].children; $('.__img_labels').each((idx, n) => $(n).remove()); + if (hasImage) { // Render each rect above the old ones, using the image as a pattern table.forEach(function (row, i) { const rect = rects[i]; @@ -790,7 +795,41 @@ Object.assign(imageElt, rects[i]); // we should probably not steal *everything*... svgRoot.appendChild(imageElt); }); + } + + if (dotChartP) { + table.forEach(function (row, i) { + // console.log('row', i, '=', row); + const rect = rects[i]; + // console.log('rect', i, '=', rect); + const num_elts = row[1]; + const rect_x = Number(rect.getAttribute('x')); + const rect_y = Number(rect.getAttribute('y')); + const rect_height = Number(rect.getAttribute('height')); + const unit_height = rect_height/num_elts; + const rect_width = Number(rect.getAttribute('width')); + const rect_fill = rect.getAttribute('fill'); + const rect_fill_opacity = Number(rect.getAttribute('fill-opacity')); + const rect_stroke = rect.getAttribute('stroke'); + const rect_stroke_width = Number(rect.getAttribute('stroke-width')); + for (let j = 0; j < num_elts; j++) { + const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); + circle.classList.add('__img_labels'); + circle.setAttribute('r', rect_width/8); + circle.setAttribute('cx', rect_x + rect_width/2); + circle.setAttribute('cy', rect_y + (num_elts - j - 0.5)*unit_height); + circle.setAttribute('fill', rect_fill); + // circle.setAttribute('fill-opacity', rect_fill_opacity); + // circle.setAttribute('stroke', rect_stroke); + // circle.setAttribute('stroke-width', rect_stroke_width); + // console.log('adding circle elt', i, j, '=', circle); + svgRoot.appendChild(circle); + } + }); + } + }); + } }; } diff --git a/test-util/pyret-programs/charts/dot-chart-test.arr b/test-util/pyret-programs/charts/dot-chart-test.arr new file mode 100644 index 00000000..3c890d7e --- /dev/null +++ b/test-util/pyret-programs/charts/dot-chart-test.arr @@ -0,0 +1,18 @@ +include chart +include image +import color as C +# include image-structs +include math + +labels = [list: "cats", "dogs", "ants", "elephants"] +count = [list: 3, 7, 4, 9] + +series = from-list.dot-chart( + labels, + count) + +img = render-chart(series).get-image() + +check: + img satisfies is-image +end diff --git a/test-util/pyret-programs/charts/image-bar-chart-test.arr b/test-util/pyret-programs/charts/image-bar-chart-test.arr index 357e109e..b102e635 100644 --- a/test-util/pyret-programs/charts/image-bar-chart-test.arr +++ b/test-util/pyret-programs/charts/image-bar-chart-test.arr @@ -1,6 +1,7 @@ include chart include image -include image-structs +import color as C +# include image-structs # color tan clashes with starter2024's trig tan include math fun f(r): star(50, "solid", "red") end @@ -19,5 +20,5 @@ img = render-chart(series) check: img satisfies is-image - color-at-position(img, 504, 258) is red + color-at-position(img, 504, 258) is C.red end From 699515f78316e15f0bb22715e9e7f99a52b98197 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 10 Oct 2024 09:00:54 -0400 Subject: [PATCH 2/5] dot-chart-test.arr: check .colors option accepted #469 --- .../pyret-programs/charts/dot-chart-test.arr | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/test-util/pyret-programs/charts/dot-chart-test.arr b/test-util/pyret-programs/charts/dot-chart-test.arr index 3c890d7e..f2972b15 100644 --- a/test-util/pyret-programs/charts/dot-chart-test.arr +++ b/test-util/pyret-programs/charts/dot-chart-test.arr @@ -7,12 +7,37 @@ include math labels = [list: "cats", "dogs", "ants", "elephants"] count = [list: 3, 7, 4, 9] -series = from-list.dot-chart( +zoo-series = from-list.dot-chart( labels, count) -img = render-chart(series).get-image() +just-red = [list: C.red] +rainbow-colors = [list: C.red, C.orange, C.yellow, C.green, C.blue, C.indigo, C.violet] +manual-colors = + [list: + C.color(51, 72, 252, 0.57), C.color(195, 180, 104, 0.87), + C.color(115, 23, 159, 0.24), C.color(144, 12, 138, 0.13), + C.color(31, 132, 224, 0.83), C.color(166, 16, 72, 0.59), + C.color(58, 193, 241, 0.98)] +fewer-colors = [list: C.red, C.green, C.blue, C.orange, C.purple] +more-colors = [list: C.red, C.green, C.blue, C.orange, C.purple, C.yellow, C.indigo, C.violet] + +fun render-image(series): + render-chart(series).get-image() +end + +zoo = render-image(zoo-series) +zoo-red = render-image(zoo-series.colors(just-red)) +zoo-rainbow = render-image(zoo-series.colors(rainbow-colors)) +zoo-manual = render-image(zoo-series.colors(manual-colors)) +zoo-fewer = render-image(zoo-series.colors(fewer-colors)) +zoo-more = render-image(zoo-series.colors(more-colors)) check: - img satisfies is-image + zoo satisfies is-image + zoo-red satisfies is-image + zoo-rainbow satisfies is-image + zoo-manual satisfies is-image + zoo-fewer satisfies is-image + zoo-more satisfies is-image end From a5adce59c91f544fc1b445bb82191a9092de1bd2 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 10 Oct 2024 16:40:10 -0400 Subject: [PATCH 3/5] dot-chart: when invoking .colors() do not introduce outline of column #469 --- src/web/js/trove/chart-lib.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/web/js/trove/chart-lib.js b/src/web/js/trove/chart-lib.js index 36f7f5ea..6f1251bc 100644 --- a/src/web/js/trove/chart-lib.js +++ b/src/web/js/trove/chart-lib.js @@ -809,9 +809,10 @@ const unit_height = rect_height/num_elts; const rect_width = Number(rect.getAttribute('width')); const rect_fill = rect.getAttribute('fill'); - const rect_fill_opacity = Number(rect.getAttribute('fill-opacity')); - const rect_stroke = rect.getAttribute('stroke'); - const rect_stroke_width = Number(rect.getAttribute('stroke-width')); + // const rect_fill_opacity = Number(rect.getAttribute('fill-opacity')); + // const rect_stroke = rect.getAttribute('stroke'); + // const rect_stroke_width = Number(rect.getAttribute('stroke-width')); + rect.setAttribute('stroke-width', 0); for (let j = 0; j < num_elts; j++) { const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.classList.add('__img_labels'); From d1ae0ab01659aa9a980c81dc1fa3e2960066e841 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Wed, 23 Oct 2024 12:13:41 -0400 Subject: [PATCH 4/5] Added num-dot-chart, where the first arg (labels) are numeric #469 --- src/web/arr/trove/chart.arr | 16 ++++++++++++++ .../pyret-programs/charts/dot-chart-test.arr | 21 ++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/web/arr/trove/chart.arr b/src/web/arr/trove/chart.arr index b226db4f..f01c0a72 100644 --- a/src/web/arr/trove/chart.arr +++ b/src/web/arr/trove/chart.arr @@ -1918,6 +1918,21 @@ fun bar-chart-from-list(labels :: P.LoS, values :: P.LoN) -> DataSeries block: data-series.make-axis(max-positive-height, max-negative-height) end +fun num-dot-chart-from-list(labels :: P.LoN, values :: P.LoN) -> DataSeries block: + doc: ``` + Consume labels, a list of numbers, and values, a list of numbers + and construct a dot chart + ``` + labels.each(check-num) + num-dot-chart-args = map2(lam(x-elt, y-elt): [list: x-elt, y-elt] end, + labels, values) + .sort-by({(x-list, y-list): x-list.get(0) < y-list.get(0)}, + {(x-list, y-list): x-list.get(0) == y-list.get(0)}) + dot-chart-from-list(map(lam(x-list): num-to-string(x-list.get(0)) end, + num-dot-chart-args), + map(lam(x-list): x-list.get(1) end, num-dot-chart-args)) +end + fun dot-chart-from-list(labels :: P.LoS, values :: P.LoN) -> DataSeries block: doc: ``` Consume labels, a list of string, and values, a list of numbers @@ -2651,6 +2666,7 @@ from-list = { image-pie-chart: image-pie-chart-from-list, bar-chart: bar-chart-from-list, dot-chart: dot-chart-from-list, + num-dot-chart: num-dot-chart-from-list, image-bar-chart: image-bar-chart-from-list, grouped-bar-chart: grouped-bar-chart-from-list, stacked-bar-chart: stacked-bar-chart-from-list, diff --git a/test-util/pyret-programs/charts/dot-chart-test.arr b/test-util/pyret-programs/charts/dot-chart-test.arr index f2972b15..e89bf268 100644 --- a/test-util/pyret-programs/charts/dot-chart-test.arr +++ b/test-util/pyret-programs/charts/dot-chart-test.arr @@ -6,10 +6,11 @@ include math labels = [list: "cats", "dogs", "ants", "elephants"] count = [list: 3, 7, 4, 9] +nlabels = [list: 2, 4, 3, 1] -zoo-series = from-list.dot-chart( - labels, - count) +zoo-series = from-list.dot-chart(labels, count) + +n-zoo-series = from-list.num-dot-chart(nlabels, count) just-red = [list: C.red] rainbow-colors = [list: C.red, C.orange, C.yellow, C.green, C.blue, C.indigo, C.violet] @@ -33,6 +34,13 @@ zoo-manual = render-image(zoo-series.colors(manual-colors)) zoo-fewer = render-image(zoo-series.colors(fewer-colors)) zoo-more = render-image(zoo-series.colors(more-colors)) +n-zoo = render-image(n-zoo-series) +n-zoo-red = render-image(n-zoo-series.colors(just-red)) +n-zoo-rainbow = render-image(n-zoo-series.colors(rainbow-colors)) +n-zoo-manual = render-image(n-zoo-series.colors(manual-colors)) +n-zoo-fewer = render-image(n-zoo-series.colors(fewer-colors)) +n-zoo-more = render-image(n-zoo-series.colors(more-colors)) + check: zoo satisfies is-image zoo-red satisfies is-image @@ -40,4 +48,11 @@ check: zoo-manual satisfies is-image zoo-fewer satisfies is-image zoo-more satisfies is-image + + n-zoo satisfies is-image + n-zoo-red satisfies is-image + n-zoo-rainbow satisfies is-image + n-zoo-manual satisfies is-image + n-zoo-fewer satisfies is-image + n-zoo-more satisfies is-image end From 3b5b288ef9f9344609c259b4985d64c51f09778d Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 24 Oct 2024 10:13:19 -0400 Subject: [PATCH 5/5] dot-chart-test.arr: Added test that color assignments are being respected #469 --- test-util/pyret-programs/charts/dot-chart-test.arr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-util/pyret-programs/charts/dot-chart-test.arr b/test-util/pyret-programs/charts/dot-chart-test.arr index e89bf268..998b5ee2 100644 --- a/test-util/pyret-programs/charts/dot-chart-test.arr +++ b/test-util/pyret-programs/charts/dot-chart-test.arr @@ -55,4 +55,9 @@ check: n-zoo-manual satisfies is-image n-zoo-fewer satisfies is-image n-zoo-more satisfies is-image + + color-at-position(zoo-rainbow, 208, 340) is C.red + color-at-position(zoo-rainbow, 322, 340) is C.orange + color-at-position(zoo-rainbow, 439, 340) is C.yellow + color-at-position(zoo-rainbow, 558, 340) is C.green end