Skip to content

Commit

Permalink
Merge pull request #554 from ds26gte/dot-charts
Browse files Browse the repository at this point in the history
An implementation of dot charts
  • Loading branch information
ds26gte authored Oct 24, 2024
2 parents 867325c + 3b5b288 commit 5db3d72
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 4 deletions.
54 changes: 54 additions & 0 deletions src/web/arr/trove/chart.arr
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,7 @@ default-bar-chart-series = {
pointer-color: none,
axisdata: none,
horizontal: false,
dot-chart: false,
default-interval-color: none
}

Expand Down Expand Up @@ -1917,6 +1918,57 @@ 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
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,
Expand Down Expand Up @@ -2613,6 +2665,8 @@ 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,
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,
Expand Down
44 changes: 42 additions & 2 deletions src/web/js/trove/chart-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -717,7 +718,7 @@
color : interval_color,
},
series : {
0 : { dataOpacity : hasImage? 0 : 1.0 }
0 : { dataOpacity : (hasImage || dotChartP)? 0 : 1.0 }
}
};

Expand Down Expand Up @@ -761,17 +762,21 @@
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
const svgRoot = chart.container.querySelector('svg');
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];
Expand All @@ -790,7 +795,42 @@
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'));
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');
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);
}
});
}

});

}
};
}
Expand Down
63 changes: 63 additions & 0 deletions test-util/pyret-programs/charts/dot-chart-test.arr
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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]
nlabels = [list: 2, 4, 3, 1]

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]
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))

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
zoo-rainbow satisfies is-image
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

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
5 changes: 3 additions & 2 deletions test-util/pyret-programs/charts/image-bar-chart-test.arr
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

0 comments on commit 5db3d72

Please sign in to comment.