custom animations for faceted specs #18
This commit is contained in:
Родитель
fa775b98d8
Коммит
90a8a20e89
|
@ -143,8 +143,8 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
/**
|
||||
* Plays animation
|
||||
*/
|
||||
function play() {
|
||||
frameIndex = 0;
|
||||
function play(startIndex) {
|
||||
frameIndex = startIndex || 0;
|
||||
const tick = () => {
|
||||
animateFrame(frameIndex);
|
||||
frameIndex++;
|
||||
|
@ -162,7 +162,7 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
clearInterval(intervalId);
|
||||
frameIndex = 0;
|
||||
}
|
||||
}, frameDuration + frameDelay);
|
||||
}, frameDuration + frameDelay + 500);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,7 +209,7 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
d3.select(controls).style("width", spec.width + transformX + 10 + "px");
|
||||
|
||||
// draw vis
|
||||
return drawChart(spec, (vegaSpec && vegaSpec.custom) ? null : vegaSpec);
|
||||
return drawChart(spec, vegaSpec && vegaSpec.custom ? null : vegaSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -247,11 +247,10 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
res();
|
||||
}
|
||||
|
||||
// ensure facet translations match in axisSelector and otherLayers
|
||||
setTimeout(() => {
|
||||
adjustAxisAndErrorbars();
|
||||
}, 100);
|
||||
|
||||
// ensure facet translations match in axisSelector and otherLayers
|
||||
setTimeout(() => {
|
||||
adjustAxisAndErrorbars();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -262,8 +261,14 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
|
||||
function adjustAxisAndErrorbars() {
|
||||
const { axisSelector, otherLayers } = getSelectors(id);
|
||||
const axisCells = d3.select(axisSelector).selectAll(".mark-group.cell>g").nodes();
|
||||
const otherLayersCells = d3.select(otherLayers).selectAll(".mark-group.cell>g").nodes();
|
||||
const axisCells = d3
|
||||
.select(axisSelector)
|
||||
.selectAll(".mark-group.cell>g")
|
||||
.nodes();
|
||||
const otherLayersCells = d3
|
||||
.select(otherLayers)
|
||||
.selectAll(".mark-group.cell>g")
|
||||
.nodes();
|
||||
|
||||
if (axisCells.length === otherLayersCells.length) {
|
||||
for (let i = 0; i < axisCells.length; i++) {
|
||||
|
@ -334,8 +339,10 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
* @param {Number} index specification index in vegaLiteSpecs
|
||||
* @returns a promise of gemini.animate
|
||||
*/
|
||||
let animating = false;
|
||||
async function animateFrame(index) {
|
||||
if (!frames[index]) return;
|
||||
if (animating) return;
|
||||
|
||||
const { axisSelector, visSelector, otherLayers, descr, slider, controls } =
|
||||
getSelectors(id);
|
||||
|
@ -365,8 +372,9 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
drawSpec(index, source).then(() => {
|
||||
timeoutId = setTimeout(() => {
|
||||
d3.select(descr).html(currMeta.description);
|
||||
|
||||
animating = true;
|
||||
anim.play(visSelector).then(() => {
|
||||
animating = false;
|
||||
d3.select(slider).property("value", index + 1);
|
||||
});
|
||||
|
||||
|
@ -476,8 +484,11 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
if (meta.custom_animation) {
|
||||
let funName = meta.custom_animation;
|
||||
let p = null;
|
||||
|
||||
if (Array.isArray(meta.custom_animation) && meta.custom_animation[0] === "quantile") {
|
||||
|
||||
if (
|
||||
Array.isArray(meta.custom_animation) &&
|
||||
meta.custom_animation[0] === "quantile"
|
||||
) {
|
||||
p = meta.custom_animation[1];
|
||||
funName = "median";
|
||||
}
|
||||
|
@ -490,24 +501,23 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
|
||||
// fake facets
|
||||
if (rawSpecsDontChange[i - 1].facet) {
|
||||
|
||||
source = {
|
||||
...vegaLiteSpecs[i - 1],
|
||||
meta: {
|
||||
...vegaLiteSpecs[i - 1].meta,
|
||||
hasFacet: true,
|
||||
columnFacet: rawSpecsDontChange[i - 1].facet.column,
|
||||
rowFacet: rawSpecsDontChange[i - 1].facet.row
|
||||
rowFacet: rawSpecsDontChange[i - 1].facet.row,
|
||||
},
|
||||
data: {
|
||||
values: vegaLiteSpecs[i - 1].data.values.map(d => {
|
||||
values: vegaLiteSpecs[i - 1].data.values.map((d) => {
|
||||
return {
|
||||
...d,
|
||||
[CONF.X_FIELD]: d[CONF.X_FIELD+"_num"],
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
[CONF.X_FIELD]: d[CONF.X_FIELD + "_num"],
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (vegaLiteSpecs[i].facet) {
|
||||
|
@ -519,12 +529,7 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
const fn = CustomAnimations[funName];
|
||||
|
||||
if (fn) {
|
||||
const sequence = await fn(
|
||||
source,
|
||||
target,
|
||||
vegaLiteSpecs[i - 1],
|
||||
p
|
||||
);
|
||||
const sequence = await fn(source, target, vegaLiteSpecs[i - 1], p);
|
||||
vegaLiteSpecs[i] = {
|
||||
custom: meta.custom_animation,
|
||||
sequence,
|
||||
|
@ -638,13 +643,7 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
};
|
||||
|
||||
for (let i = 1; i < vegaSpecs.length; i++) {
|
||||
let prev = vegaSpecs[i - 1];
|
||||
|
||||
if (prev.custom) {
|
||||
const seq = prev.sequence;
|
||||
prev = seq[seq.length - 1];
|
||||
}
|
||||
|
||||
const prev = vegaSpecs[i - 1];
|
||||
const curr = vegaSpecs[i];
|
||||
|
||||
const prevMeta = metas[i - 1];
|
||||
|
@ -657,12 +656,12 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
resp = await gemini.recommendForSeq(curr.sequence, {
|
||||
...options,
|
||||
stageN: curr.sequence.length - 1,
|
||||
totalDuration: options.totalDuration,
|
||||
totalDuration: frameDuration,
|
||||
});
|
||||
|
||||
const _gemSpec = resp[0].specs.map((d) => d.spec);
|
||||
|
||||
// make sure to add gemini_id to data change.
|
||||
// make sure to add gemini_id to data change.
|
||||
// gemini recommend does not add it by itself.
|
||||
_gemSpec.forEach((d) => {
|
||||
if (d.timeline.concat.length) {
|
||||
|
@ -685,9 +684,13 @@ function App(id, { specUrls, specs, autoPlay = false, frameDur, frameDel }) {
|
|||
prevMeta,
|
||||
currMeta,
|
||||
});
|
||||
} else {
|
||||
resp = await gemini.recommend(prev, curr, options);
|
||||
|
||||
} else {
|
||||
resp = await gemini.recommend(
|
||||
prev.custom ? prev.sequence[prev.sequence.length - 1] : prev,
|
||||
curr,
|
||||
options
|
||||
);
|
||||
if (prev.custom) console.log(resp);
|
||||
const _gemSpec = resp[0] ? resp[0].spec : gemSpec;
|
||||
|
||||
const sync = _gemSpec.timeline.concat[0].sync;
|
||||
|
|
|
@ -148,8 +148,8 @@ const getMedianStep = (source, target, step = 0, p = 0.5) => {
|
|||
groupId,
|
||||
median_pos: y_median_pos,
|
||||
rankDiff: Math.abs(max_rank - median_rank),
|
||||
rule_start: d3.min(sorted, d => d[CONF.X_FIELD + "_pos"]),
|
||||
rule_end: d3.max(sorted, d => d[CONF.X_FIELD + "_pos"]),
|
||||
rule_start: d3.min(sorted, d => d[CONF.X_FIELD + "_pos"]) + 1,
|
||||
rule_end: d3.max(sorted, d => d[CONF.X_FIELD + "_pos"]) - 1,
|
||||
});
|
||||
|
||||
values.push(...sorted);
|
||||
|
@ -168,7 +168,7 @@ const getMedianStep = (source, target, step = 0, p = 0.5) => {
|
|||
}
|
||||
|
||||
d3
|
||||
.rollups(
|
||||
.rollup(
|
||||
source.data.values.slice(),
|
||||
reduce,
|
||||
...groupKeys.map((key) => {
|
||||
|
@ -178,8 +178,15 @@ const getMedianStep = (source, target, step = 0, p = 0.5) => {
|
|||
|
||||
const rules = [];
|
||||
|
||||
let ruleField = isLast ? "y_median" : CONF.Y_FIELD;
|
||||
|
||||
if (hasFacet) {
|
||||
ruleField = isLast ? "y_median_pos" : CONF.Y_FIELD;
|
||||
}
|
||||
|
||||
all_groups.forEach((d, i) => {
|
||||
const n = all_groups.length;
|
||||
|
||||
|
||||
const top_rule = {
|
||||
transform: isLast ? [{ filter: d.groupFilter }] : [{ filter: d.filter }],
|
||||
|
@ -191,7 +198,7 @@ const getMedianStep = (source, target, step = 0, p = 0.5) => {
|
|||
},
|
||||
encoding: {
|
||||
y: {
|
||||
field: isLast ? "y_median" : hasFacet ? "y_median_pos" : CONF.Y_FIELD,
|
||||
field: ruleField,
|
||||
type: "quantitative",
|
||||
aggregate: "max",
|
||||
axis: null,
|
||||
|
@ -209,7 +216,7 @@ const getMedianStep = (source, target, step = 0, p = 0.5) => {
|
|||
},
|
||||
encoding: {
|
||||
y: {
|
||||
field: isLast ? "y_median" : hasFacet ? "y_median_pos" : CONF.Y_FIELD,
|
||||
field: ruleField,
|
||||
type: "quantitative",
|
||||
aggregate: "min",
|
||||
axis: null,
|
||||
|
@ -429,37 +436,100 @@ const getMinMaxStep = (source, target, minOrMax = "min") => {
|
|||
const domain = source.encoding.y.scale.domain;
|
||||
const minMaxPoints = {};
|
||||
|
||||
const all_groups = d3.groups(
|
||||
source.data.values.slice(),
|
||||
(d) => d[CONF.X_FIELD])
|
||||
.map(([key, data]) => {
|
||||
const groupValue = key;
|
||||
const filter = `datum['${CONF.X_FIELD}'] === ${groupValue}`;
|
||||
const aggr = aggrFn(data, d => d[CONF.Y_FIELD]);
|
||||
const groupKeys = [CONF.X_FIELD];
|
||||
const hasFacet = source.meta.hasFacet;
|
||||
const meta = source.meta;
|
||||
|
||||
minMaxPoints[groupValue] = data.find(d => d[CONF.Y_FIELD] === aggr);
|
||||
const values = [];
|
||||
|
||||
return {
|
||||
if (hasFacet) {
|
||||
if (meta.columnFacet) {
|
||||
groupKeys.push(meta.columnFacet.field);
|
||||
}
|
||||
|
||||
if (meta.rowFacet) {
|
||||
groupKeys.push(meta.rowFacet.field);
|
||||
}
|
||||
}
|
||||
|
||||
const all_groups = [];
|
||||
|
||||
d3.rollup(
|
||||
source.data.values.slice(),
|
||||
data => {
|
||||
const groupValue = data[0][CONF.X_FIELD];
|
||||
let filter = `datum['${CONF.X_FIELD}'] === ${groupValue}`;
|
||||
let groupId = groupValue;
|
||||
|
||||
if (hasFacet) {
|
||||
filter += ' && ';
|
||||
groupId += "_";
|
||||
|
||||
if (meta.columnFacet) {
|
||||
filter += `datum['${meta.columnFacet.field}'] === '${data[0][meta.columnFacet.field]}'`;
|
||||
groupId += data[0][meta.columnFacet.field];
|
||||
}
|
||||
|
||||
if (meta.columnFacet && meta.rowFacet) {
|
||||
filter += ' && ';
|
||||
groupId += "_";
|
||||
}
|
||||
|
||||
if (meta.rowFacet) {
|
||||
filter += `datum['${meta.rowFacet.field}'] === '${data[0][meta.rowFacet.field]}'`
|
||||
groupId += data[0][meta.rowFacet.field];
|
||||
}
|
||||
}
|
||||
|
||||
const aggr = aggrFn(data, d => {
|
||||
return hasFacet ? d.oldY : d[CONF.Y_FIELD];
|
||||
});
|
||||
const aggr_pos = hasFacet ? data[0].scaleY(aggr) : aggr;
|
||||
|
||||
all_groups.push({
|
||||
filter,
|
||||
groupValue,
|
||||
groupKey: CONF.X_FIELD,
|
||||
aggr,
|
||||
}
|
||||
});
|
||||
aggr_pos,
|
||||
groupId,
|
||||
rule_start: d3.min(data, d => d[CONF.X_FIELD] - 2),
|
||||
rule_end: d3.max(data, d => d[CONF.X_FIELD] + 2),
|
||||
});
|
||||
|
||||
const g = data.find(d => {
|
||||
const v = hasFacet ? d.oldY : d[CONF.Y_FIELD];
|
||||
return v === aggr;
|
||||
});
|
||||
|
||||
values.push(...data.map(d => {
|
||||
const isAggr = g && g.gemini_id === d.gemini_id;
|
||||
|
||||
return {
|
||||
...d,
|
||||
isAggr,
|
||||
aggr_pos
|
||||
}
|
||||
}))
|
||||
},
|
||||
...groupKeys.map((key) => {
|
||||
return (d) => d[key];
|
||||
})
|
||||
);
|
||||
|
||||
const rules = all_groups.map((group, i) => {
|
||||
const n = all_groups.length;
|
||||
return {
|
||||
transform: [{ filter: group.filter }],
|
||||
name: `rule_${group.groupValue}`,
|
||||
name: `rule_${group.groupId}`,
|
||||
mark: {
|
||||
type: "rule",
|
||||
x: { expr: `${i + 1} * (width / ${n + 1}) - 5` },
|
||||
x2: { expr: `${i + 1} * (width / ${n + 1}) + 5` },
|
||||
x: hasFacet ? group.rule_start : { expr: `${i + 1} * (width / ${n + 1}) - 5` },
|
||||
x2: hasFacet ? group.rule_end : { expr: `${i + 1} * (width / ${n + 1}) + 5` },
|
||||
},
|
||||
encoding: {
|
||||
y: {
|
||||
field: CONF.Y_FIELD,
|
||||
field: hasFacet ? "aggr_pos" : CONF.Y_FIELD,
|
||||
type: "quantitative",
|
||||
aggregate: minOrMax,
|
||||
axis: null,
|
||||
|
@ -469,16 +539,6 @@ const getMinMaxStep = (source, target, minOrMax = "min") => {
|
|||
}
|
||||
});
|
||||
|
||||
const values = source.data.values.map(d => {
|
||||
const g = minMaxPoints[d[CONF.X_FIELD]];
|
||||
const isAggr = g && g.gemini_id === d.gemini_id;
|
||||
|
||||
return {
|
||||
...d,
|
||||
isAggr,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
$schema: CONF.SCHEME,
|
||||
width,
|
||||
|
@ -518,7 +578,7 @@ const CustomAnimations = {
|
|||
},
|
||||
min: (rawSource, target, source) => {
|
||||
const step_1 = getMinMaxStep(rawSource, target, "min");
|
||||
const groups = step_1.meta.all_groups;
|
||||
// const groups = step_1.meta.all_groups;
|
||||
|
||||
const step_2 = {
|
||||
...step_1,
|
||||
|
@ -542,20 +602,20 @@ const CustomAnimations = {
|
|||
|
||||
// this is for test, it should be passed from R or Python side..
|
||||
// but can also keep this. it will work!!!
|
||||
target.data.values.forEach((d) => {
|
||||
const group = groups.find((x) => x.groupValue === d[x.groupKey]);
|
||||
d[CONF.Y_FIELD] = group.aggr;
|
||||
});
|
||||
// target.data.values.forEach((d) => {
|
||||
// const group = groups.find((x) => x.groupValue === d[x.groupKey]);
|
||||
// d[CONF.Y_FIELD] = group.aggr;
|
||||
// });
|
||||
|
||||
const domain = d3.extent(groups, (d) => d.aggr);
|
||||
target.encoding.y.scale.domain = domain;
|
||||
// const domain = d3.extent(groups, (d) => d.aggr);
|
||||
// target.encoding.y.scale.domain = domain;
|
||||
/// end of test ////
|
||||
|
||||
return [rawSource, step_1, step_2, target];
|
||||
},
|
||||
max: (rawSource, target, source) => {
|
||||
const step_1 = getMinMaxStep(rawSource, target, "max");
|
||||
const groups = step_1.meta.all_groups;
|
||||
// const groups = step_1.meta.all_groups;
|
||||
|
||||
const step_2 = {
|
||||
...step_1,
|
||||
|
@ -692,7 +752,7 @@ const CustomAnimations = {
|
|||
}
|
||||
};
|
||||
|
||||
return [rawSource, intermediate, step_1, step_2, step_3, step_4];
|
||||
return [rawSource, intermediate, step_1, step_2, step_3, step_4, target];
|
||||
},
|
||||
median: (rawSource, target, calculatedSource, p) => {
|
||||
const initial = getMedianStep(rawSource, target, 0, p ?? 0.5);
|
||||
|
@ -700,8 +760,6 @@ const CustomAnimations = {
|
|||
// const groups = initial.meta.all_groups;
|
||||
const last_with_points = getMedianStep(rawSource, target, null, p ?? 0.5)
|
||||
|
||||
console.log(last_with_points);
|
||||
|
||||
// this is for test, it should be passed from R or Python side..
|
||||
// target.data.values.forEach((d) => {
|
||||
// const group = groups.find((x) => x.groupValue === d[x.groupKey]);
|
||||
|
@ -726,7 +784,23 @@ const CustomAnimations = {
|
|||
}
|
||||
},
|
||||
resolve: { axis: { y: "independent" } },
|
||||
}
|
||||
};
|
||||
|
||||
const source = {
|
||||
$schema: rawSource.$schema,
|
||||
data: rawSource.data,
|
||||
width: rawSource.width,
|
||||
height: rawSource.height,
|
||||
meta: rawSource.meta,
|
||||
layer: [
|
||||
{
|
||||
name: 'main',
|
||||
mark: rawSource.mark,
|
||||
encoding: rawSource.encoding,
|
||||
}
|
||||
],
|
||||
resolve: { axis: { y: "independent" } },
|
||||
};
|
||||
|
||||
return [rawSource, initial, last_with_points, last, target];
|
||||
},
|
||||
|
|
|
@ -214,10 +214,6 @@ function getHackedSpec({ view, spec, width = 600, height = 600 }) {
|
|||
|
||||
const xStart = colMap.get(col) || 0;
|
||||
const yStart = rowMap.get(row) || 0;
|
||||
|
||||
if (spec.meta.custom_animation) {
|
||||
console.log(yStart);
|
||||
}
|
||||
|
||||
const xField = spec.meta.parse === "jitter" ? "x" : CONF.X_FIELD;
|
||||
const yField = spec.meta.parse === "jitter" ? "y" : CONF.Y_FIELD;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -176,14 +176,14 @@
|
|||
<script src="../js/custom-animations.js"></script>
|
||||
<script src="../js/app.js"></script>
|
||||
<script>
|
||||
const animationType = "quantile";
|
||||
const animationType = "median";
|
||||
const base = "../../../sandbox/custom_animations";
|
||||
let app1 = null;
|
||||
|
||||
d3.json(
|
||||
`${base}/custom-animations-${animationType}-R.json`
|
||||
`${base}/custom-animations-${animationType}-facet.json`
|
||||
).then(specs => {
|
||||
app1 = App("app", { specs: specs.slice(0), frameDur: 3000 });
|
||||
app1 = App("app", { specs: specs, frameDur: 3000, frameDel: 1000 });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -67,7 +67,7 @@
|
|||
},
|
||||
"spec": {
|
||||
"height": 300,
|
||||
"width": 150,
|
||||
"width": 100,
|
||||
"mark": {
|
||||
"type": "point",
|
||||
"filled": true,
|
||||
|
@ -146,8 +146,8 @@
|
|||
}
|
||||
},
|
||||
"spec": {
|
||||
"height": 150,
|
||||
"width": 150,
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
"mark": {
|
||||
"type": "point",
|
||||
"filled": true,
|
||||
|
@ -293,8 +293,8 @@
|
|||
}
|
||||
},
|
||||
"spec": {
|
||||
"height": 150,
|
||||
"width": 150,
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
"mark": {
|
||||
"type": "point",
|
||||
"filled": true,
|
||||
|
@ -3452,8 +3452,8 @@
|
|||
}
|
||||
},
|
||||
"spec": {
|
||||
"height": 150,
|
||||
"width": 150,
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
"mark": {
|
||||
"type": "point",
|
||||
"filled": true,
|
||||
|
@ -3476,7 +3476,7 @@
|
|||
"y": {
|
||||
"field": "datamations_y",
|
||||
"type": "quantitative",
|
||||
"title": "mean(bill_length_mm + something)",
|
||||
"title": "mean bill_length",
|
||||
"scale": {
|
||||
"domain": [32.1, 59.6]
|
||||
}
|
||||
|
@ -6630,8 +6630,8 @@
|
|||
}
|
||||
},
|
||||
"spec": {
|
||||
"height": 150,
|
||||
"width": 150,
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
"mark": {
|
||||
"type": "point",
|
||||
"filled": true,
|
||||
|
@ -6654,7 +6654,7 @@
|
|||
"y": {
|
||||
"field": "datamations_y",
|
||||
"type": "quantitative",
|
||||
"title": "mean(bill_length_mm + something)",
|
||||
"title": "mean bill_length",
|
||||
"scale": {
|
||||
"domain": [32.1, 59.6]
|
||||
}
|
||||
|
@ -10837,8 +10837,8 @@
|
|||
}
|
||||
},
|
||||
"spec": {
|
||||
"height": 150,
|
||||
"width": 150,
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
"layer": [
|
||||
{
|
||||
"mark": "errorbar",
|
||||
|
@ -10859,7 +10859,7 @@
|
|||
"y": {
|
||||
"field": "datamations_y_raw",
|
||||
"type": "quantitative",
|
||||
"title": "mean(bill_length_mm + something)",
|
||||
"title": "mean bill_length",
|
||||
"scale": {
|
||||
"domain": [32.1, 59.6]
|
||||
}
|
||||
|
@ -10925,7 +10925,7 @@
|
|||
"y": {
|
||||
"field": "datamations_y",
|
||||
"type": "quantitative",
|
||||
"title": "mean(bill_length_mm + something)",
|
||||
"title": "mean bill_length",
|
||||
"scale": {
|
||||
"domain": [32.1, 59.6]
|
||||
}
|
||||
|
@ -15120,8 +15120,8 @@
|
|||
}
|
||||
},
|
||||
"spec": {
|
||||
"height": 150,
|
||||
"width": 150,
|
||||
"height": 100,
|
||||
"width": 100,
|
||||
"layer": [
|
||||
{
|
||||
"mark": "errorbar",
|
||||
|
@ -15142,7 +15142,7 @@
|
|||
"y": {
|
||||
"field": "datamations_y_raw",
|
||||
"type": "quantitative",
|
||||
"title": "mean(bill_length_mm + something)",
|
||||
"title": "mean bill_length",
|
||||
"scale": {
|
||||
"domain": [36.481220238402, 51.3624372072661]
|
||||
}
|
||||
|
@ -15208,7 +15208,7 @@
|
|||
"y": {
|
||||
"field": "datamations_y",
|
||||
"type": "quantitative",
|
||||
"title": "mean(bill_length_mm + something)",
|
||||
"title": "mean bill_length",
|
||||
"scale": {
|
||||
"domain": [36.481220238402, 51.3624372072661]
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1041,7 +1041,10 @@
|
|||
"axes": false,
|
||||
"description": "Plot 10% quantile of each group",
|
||||
"splitField": "Degree",
|
||||
"custom_animation": "quantile(0.1)"
|
||||
"custom_animation": [
|
||||
"quantile",
|
||||
0.1
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"values": [
|
||||
|
|
Загрузка…
Ссылка в новой задаче